Compare commits

..

52 Commits

Author SHA1 Message Date
e37a974d9c [이민권] 계정 삭제
- 계정 삭제 시, Firebase 정보 삭제
2024-01-12 12:31:21 +09:00
470591cb44 [이민권] 계정 삭제
- 계정 삭제 취소 기능 추가
2024-01-11 19:11:43 +09:00
38114769b3 [이민권] 계정 삭제
- 계정 삭제 취소 기능 추가
2024-01-11 12:54:48 +09:00
b05473a1c6 [이민권] 계정 삭제
- 계정 삭제 취소 기능 추가
2024-01-10 18:40:38 +09:00
0fff694e8a [이민권] 계정 삭제
- 계정 삭제 취소 기능 추가
2024-01-10 18:10:18 +09:00
d8713298c4 [이민권] 계정 삭제
- 계정 삭제 취소 기능 추가
2024-01-10 17:35:00 +09:00
763b0fc4bd [이민권] 계정 삭제
- 계정 삭제 취소 기능 추가
2024-01-10 17:28:24 +09:00
d8e18d7ffc [이민권] 서비스 준비
- 계정 삭제 시, TTL 적용
2024-01-09 19:56:57 +09:00
381f1edb80 [이민우] 정보가 아니라 주소갑이 나와서 수정 (대리 커밋) 2023-12-08 17:08:14 +09:00
a8e448b8ca 로그 정리 2023-10-30 17:25:39 +09:00
9b0e3a5d5b 모듈 정리 2023-10-30 17:25:31 +09:00
5757a81cb8 fba, template 폴더를 package 에 추가 2023-10-24 14:05:42 +09:00
c4b8e630dc [이민권] 에러 수정 2023-10-23 15:17:07 +09:00
43b0242652 Firebase-Google Analaytics Desktop 버전 연동을 위해서 JavaScript SDK( JS-SDk ) 관련 코드 추가 2023-10-23 15:08:48 +09:00
893744a1ab Revert "모듈 업데이트"
This reverts commit 192cc569b4.
2023-10-12 12:04:49 +09:00
192cc569b4 모듈 업데이트 2023-10-12 12:04:31 +09:00
56e6608537 화이트리스트 추가,삭제 반영 안되는 문제 수정 2023-10-05 11:08:09 +09:00
0053033e32 현재 block된 정보를 조회 2023-09-25 12:29:41 +09:00
d1ff6a56fc gocommon 업데이트 반영 2023-09-21 13:18:41 +09:00
114461c51d 화이트리스트 추가 안되는 문제 수정 2023-09-12 17:07:16 +09:00
117a3e5d90 [이민우] 캐릭터 생성 제한 구현 (대신 커밋) 2023-09-08 18:24:17 +09:00
fafc463f2a [이민우] 캐릭터 생성 제한 구현 (대신 커밋) 2023-09-06 19:34:47 +09:00
e2bec481f0 [이민우] 캐릭터 생성 제한 구현 (대신 커밋) 2023-09-06 17:11:47 +09:00
89e7d35b5a [이민권] 오타 수정 2023-09-06 11:35:21 +09:00
fe662c5355 [이민우] 캐릭터 생성 제한 구현 (대신 커밋) 2023-09-06 11:23:06 +09:00
6767a37704 version split 수정 2023-09-05 17:14:56 +09:00
2ea035a43b version split 수정 2023-09-04 11:15:23 +09:00
2a1ad499ed [이민우] 캐릭터 생성 제한 구현 (대신 커밋) 2023-08-31 17:19:18 +09:00
5cc6ddc8f1 [이민우] 캐릭터 생성 제한 구현 (대신 커밋) 2023-08-31 11:55:31 +09:00
0e070221f8 Merge branch 'master' into kd-live 2023-08-29 18:41:22 +09:00
1ba32aa4c9 쿠폰 조회 오류 수정 2023-08-29 11:06:14 +09:00
edb3e07329 parsemuiltipartform 추가 . 에러는 무시 2023-08-28 13:53:59 +09:00
f5304fae80 쿠폰 api를 maingate 로 옮김 2023-08-25 12:31:32 +09:00
d9be04541b prepare 로그 자세히 2023-08-25 11:39:17 +09:00
d2e06961b9 [이민권] guest 계정에 link 걸면 guest link는 제거하도록 수정 2023-08-24 16:45:01 +09:00
28092fcf17 [이민권] 게스트 계정 링크 시 삭제 기능 추가 2023-08-24 15:39:11 +09:00
a7a20aebcf 모듈 업데이트 2023-08-23 22:46:07 +09:00
c43c10982c [이민권] link 수정 2023-08-23 19:03:28 +09:00
02c4f9e3d1 [이민권] link, unlink 이슈 수정 2023-08-23 18:53:24 +09:00
1db22730aa [이민권] linkinfo 이슈 수정 2023-08-23 18:46:23 +09:00
184675a9b7 계정 제재 동작 오류 수정 2023-08-23 17:48:47 +09:00
197ee7127b 서버 noauth 플래그를 알림 2023-08-22 19:53:30 +09:00
869fa48d74 Revert "[이민권] 임시 로그 삽입"
This reverts commit 7470f8e001.
2023-08-22 18:32:37 +09:00
7470f8e001 [이민권] 임시 로그 삽입 2023-08-22 18:27:04 +09:00
fc70a9482c [이민권] getProviderInfo 실패시 리턴하지 않는 부분 수정 2023-08-22 18:22:31 +09:00
e3afb58634 file 경로에서 servicecode 제거 2023-08-22 18:06:14 +09:00
bafb67dabc 스팀 로그인 noauth 추가 처리 2023-08-22 17:51:41 +09:00
08cb989975 서비스 json 수정 2023-08-22 11:10:59 +09:00
455011fd99 계정 제재 api추가 2023-08-22 10:16:09 +09:00
e8832f329a 안쓰는 인덱스 제거 2023-08-21 11:01:21 +09:00
a8df7d54bd version split 디폴트 이름 변경 2023-08-18 13:56:34 +09:00
39e1b925e5 divisionsSerialized가 잘못 덮어씌어진 문제 수정 2023-08-17 12:35:48 +09:00
23 changed files with 9617 additions and 363 deletions

View File

@ -0,0 +1,37 @@
// Import the functions you need from the SDKs you need
import { initializeApp } from './firebase-app.js';
import { getAnalytics, logEvent } from './firebase-analytics.js';
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "{{.FBA_apiKey}}",
authDomain: "{{.FBA_authDomain}}",
databaseURL: "{{.FBA_databaseURL}}",
projectId: "{{.FBA_projectId}}",
storageBucket: "{{.FBA_storageBucket}}",
messagingSenderId: "{{.FBA_messagingSenderId}}",
appId: "{{.FBA_appId}}",
measurementId: "{{.FBA_measurementId}}"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
// LogEvent('DESKTOP_TEST8');
export function LogEvent(args){
if ( arguments.length == 1) {
logEvent(analytics, arguments[0]);
} else {
logEvent(analytics, arguments[0], arguments[1]);
}
}

1
backup/firebase-jssdk/fb-ga.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

537
backup/firebase-jssdk/js.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
{ {
"maingate_mongodb_url": "mongodb://...", "maingate_mongodb_url": "mongodb://...",
"autologin_ttl": 604800, "autologin_ttl": 604800,
"acc_del_ttl": 7776000,
"maximum_num_link_account": 10, "maximum_num_link_account": 10,
"redirect_base_url": "", "redirect_base_url": "",
"google_client_id" : "", "google_client_id" : "",
@ -25,6 +26,15 @@
"firebase_admin_sdk_credentialfile": "", "firebase_admin_sdk_credentialfile": "",
"firebase_google_analytics_jssdk_apikey": "",
"firebase_google_analytics_jssdk_authdomain": "",
"firebase_google_analytics_jssdk_databaseurl": "",
"firebase_google_analytics_jssdk_projectid": "",
"firebase_google_analytics_jssdk_storagebucket": "",
"firebase_google_analytics_jssdk_messagingsenderid": "",
"firebase_google_analytics_jssdk_apiid": "",
"firebase_google_analytics_jssdk_measurementid": "",
"maingate_global_admins" : [ "maingate_global_admins" : [
"mountain@action2quare.com" "mountain@action2quare.com"
] ]

View File

@ -2,9 +2,7 @@ package core
import ( import (
"bytes" "bytes"
"crypto/md5"
"encoding/binary" "encoding/binary"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -27,7 +25,6 @@ import (
) )
type FileDocumentDesc struct { type FileDocumentDesc struct {
Service string `bson:"service" json:"service"`
Key string `bson:"key" json:"key"` Key string `bson:"key" json:"key"`
Src string `bson:"src" json:"src"` Src string `bson:"src" json:"src"`
Link string `bson:"link" json:"link"` Link string `bson:"link" json:"link"`
@ -110,11 +107,6 @@ var seq = uint32(0)
func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error { func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error {
if r.Method == "PUT" { if r.Method == "PUT" {
servicename := r.FormValue("service")
hasher := md5.New()
hasher.Write([]byte(servicename))
subfolder := hex.EncodeToString(hasher.Sum(nil))[:8]
infile, header, err := r.FormFile("file") infile, header, err := r.FormFile("file")
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
@ -130,17 +122,16 @@ func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error
var b [5]byte var b [5]byte
binary.BigEndian.PutUint32(b[0:4], uint32(time.Now().Unix())) binary.BigEndian.PutUint32(b[0:4], uint32(time.Now().Unix()))
b[4] = byte(atomic.AddUint32(&seq, 1) % 255) b[4] = byte(atomic.AddUint32(&seq, 1) % 255)
rf := hex.EncodeToString(b[1:])
newidstr := subfolder + rf
newidbt, _ := hex.DecodeString(newidstr)
newidobj := primitive.NewObjectID()
copy(newidobj[:], newidbt[:8])
newidobj := primitive.NewObjectID()
copy(newidobj[:], b[1:])
rf := newidobj.Hex()
var link string var link string
if extract { if extract {
link = path.Join("static", subfolder, rf) link = path.Join("static", rf)
} else { } else {
link = path.Join("static", subfolder, rf, header.Filename) link = path.Join("static", rf, header.Filename)
} }
newdoc := FileDocumentDesc{ newdoc := FileDocumentDesc{
@ -151,11 +142,9 @@ func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error
Link: link, Link: link,
Desc: desc, Desc: desc,
Key: rf, Key: rf,
Service: servicename,
} }
_, _, err = caller.mg.mongoClient.UpsertOne(CollectionFile, bson.M{ _, _, err = caller.mg.mongoClient.UpsertOne(CollectionFile, bson.M{
"_id": newidobj, "_id": newidobj,
"service": servicename,
"key": rf, "key": rf,
}, newdoc) }, newdoc)
@ -169,45 +158,88 @@ func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error
return nil return nil
} }
func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error { func (caller apiCaller) blockAPI(w http.ResponseWriter, r *http.Request) error {
mg := caller.mg mg := caller.mg
if r.Method == "GET" { if r.Method == "GET" {
// if !caller.isAdminOrValidToken() { target, ok := gocommon.ReadObjectIDFormValue(r.Form, "accid")
// logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo) if ok {
// w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(mg.bl.all())
// return nil } else if !target.IsZero() {
// } if blocked, ok := mg.bl.get(target); ok && blocked != nil {
json.NewEncoder(w).Encode(blocked)
}
}
} else if r.Method == "PUT" {
body, _ := io.ReadAll(r.Body)
all, err := mg.mongoClient.All(CollectionWhitelist) var bipl blockinfoWithStringId
if err := json.Unmarshal(body, &bipl); err != nil {
return err
}
accid, err := primitive.ObjectIDFromHex(bipl.StrId)
if err != nil { if err != nil {
return err return err
} }
if len(all) > 0 { bi := blockinfo{
var notexp []primitive.M Start: primitive.NewDateTimeFromTime(time.Unix(bipl.StartUnix, 0)),
for _, v := range all { End: primitive.NewDateTimeFromTime(time.Unix(bipl.EndUnix, 0)),
if _, exp := v["_ts"]; !exp { Reason: bipl.Reason,
notexp = append(notexp, v)
} }
logger.Println("bi :", accid, bi)
_, _, err = mg.mongoClient.Update(CollectionBlock, bson.M{
"_id": accid,
}, bson.M{
"$set": &bi,
}, options.Update().SetUpsert(true))
if err != nil {
return err
} }
allraw, _ := json.Marshal(notexp) } else if r.Method == "DELETE" {
w.Write(allraw) id := r.URL.Query().Get("id")
if len(id) == 0 {
return errors.New("id param is missing")
} }
idobj, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
_, _, err = mg.mongoClient.Update(CollectionBlock, bson.M{
"_id": idobj,
}, bson.M{
"$currentDate": bson.M{
"_ts": bson.M{"$type": "date"},
},
}, options.Update().SetUpsert(false))
if err != nil {
return err
}
mg.mongoClient.Delete(CollectionAuth, bson.M{"_id": idobj})
}
return nil
}
func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error {
mg := caller.mg
if r.Method == "GET" {
enc := json.NewEncoder(w)
enc.Encode(mg.wl.all())
} else if r.Method == "PUT" { } else if r.Method == "PUT" {
body, _ := io.ReadAll(r.Body) body, _ := io.ReadAll(r.Body)
var member whitelistmember var member whitelistmember
if err := json.Unmarshal(body, &member); err != nil { if err := json.Unmarshal(body, &member); err != nil {
return err return err
} }
member.ExpiredAt = 0
// if !caller.isAdminOrValidToken() { member.Id = primitive.NilObjectID
// logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo)
// w.WriteHeader(http.StatusUnauthorized)
// return nil
// }
member.Expired = 0
_, _, err := mg.mongoClient.Update(CollectionWhitelist, bson.M{ _, _, err := mg.mongoClient.Update(CollectionWhitelist, bson.M{
"_id": primitive.NewObjectID(), "_id": primitive.NewObjectID(),
}, bson.M{ }, bson.M{
@ -260,7 +292,7 @@ func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error
atomic.StorePointer(&mg.serviceptr, unsafe.Pointer(&newService)) atomic.StorePointer(&mg.serviceptr, unsafe.Pointer(&newService))
} }
w.Write(mg.service().divisionsSerialized) w.Write(mg.service().serviceSerialized)
} else if r.Method == "POST" { } else if r.Method == "POST" {
body, _ := io.ReadAll(r.Body) body, _ := io.ReadAll(r.Body)
var service serviceDescription var service serviceDescription
@ -319,6 +351,38 @@ func (caller apiCaller) maintenanceAPI(w http.ResponseWriter, r *http.Request) e
return nil return nil
} }
func (caller apiCaller) couponAPI(w http.ResponseWriter, r *http.Request) error {
switch r.Method {
case "PUT":
// 쿠폰 생성
logger.Println("begin generateCoupons")
generateCoupons(caller.mg.mongoClient, w, r)
case "POST":
// TODO : 쿠폰 사용
// 쿠폰 사용 표시 해주고 내용을 응답
logger.Println("begin useCoupon")
useCoupon(caller.mg.mongoClient, w, r)
case "GET":
// 쿠폰 조회
if r.Form.Has("code") {
// 쿠폰 코드 조회
logger.Println("begin queryCoupon")
queryCoupon(caller.mg.mongoClient, w, r)
} else if r.Form.Has("name") {
// 쿠폰 코드 다운
logger.Println("begin downloadCoupons")
downloadCoupons(caller.mg.mongoClient, w, r)
} else {
// 쿠폰 이름 목록
logger.Println("begin listAllCouponNames")
listAllCouponNames(caller.mg.mongoClient, w, r)
}
}
return nil
}
var errApiTokenMissing = errors.New("mg-x-api-token is missing") var errApiTokenMissing = errors.New("mg-x-api-token is missing")
func (caller apiCaller) configAPI(w http.ResponseWriter, r *http.Request) error { func (caller apiCaller) configAPI(w http.ResponseWriter, r *http.Request) error {
@ -339,6 +403,40 @@ func (caller apiCaller) configAPI(w http.ResponseWriter, r *http.Request) error
return nil return nil
} }
func (caller apiCaller) lockcreatecharAPI(w http.ResponseWriter, r *http.Request) error {
mg, err := caller.mg.mongoClient.FindAll(CollectionService, bson.M{})
if err != nil {
return err
}
haschr, _ := gocommon.ReadStringFormValue(r.Form, "haschr")
locked := make(map[string]any)
if haschr == "true" {
locked["lock"] = false
} else {
curregion, _ := gocommon.ReadStringFormValue(r.Form, "region")
for _, regioninfo := range mg {
region := regioninfo["divisions"].(primitive.M)
for idx, rl := range region {
if idx == curregion {
if rl.(primitive.M)["lockcreatechar"].(bool) {
locked["lock"] = true
} else {
locked["lock"] = false
}
}
}
}
}
create, _ := json.Marshal(locked)
w.Write(create)
return nil
}
type apiCaller struct { type apiCaller struct {
userinfo map[string]any userinfo map[string]any
globalAdmins map[string]bool globalAdmins map[string]bool
@ -359,6 +457,8 @@ func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) {
r.Body.Close() r.Body.Close()
}() }()
r.ParseMultipartForm(32 << 20)
var userinfo map[string]any var userinfo map[string]any
if !*devflag { if !*devflag {
@ -439,6 +539,12 @@ func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) {
err = caller.maintenanceAPI(w, r) err = caller.maintenanceAPI(w, r)
} else if strings.HasSuffix(r.URL.Path, "/files") { } else if strings.HasSuffix(r.URL.Path, "/files") {
err = caller.filesAPI(w, r) err = caller.filesAPI(w, r)
} else if strings.HasSuffix(r.URL.Path, "/block") {
err = caller.blockAPI(w, r)
} else if strings.HasSuffix(r.URL.Path, "/coupon") {
err = caller.couponAPI(w, r)
} else if strings.HasSuffix(r.URL.Path, "/lockcreatechar") {
err = caller.lockcreatecharAPI(w, r)
} }
if err != nil { if err != nil {

372
core/api_coupon.go Normal file
View File

@ -0,0 +1,372 @@
package core
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"repositories.action2quare.com/ayo/gocommon"
coupon "repositories.action2quare.com/ayo/gocommon/coupon"
"repositories.action2quare.com/ayo/gocommon/logger"
)
const (
CollectionCoupon = gocommon.CollectionName("coupon")
CollectionCouponUse = gocommon.CollectionName("coupon_use")
)
type couponDoc struct {
Name string `json:"name" bson:"name"`
Effect string `json:"effect" bson:"effect"`
Desc string `json:"desc" bson:"desc"`
Total int64 `json:"total" bson:"total"`
Remains []string `json:"remains,omitempty" bson:"remains,omitempty"`
Used []string `json:"used,omitempty" bson:"used,omitempty"`
}
func makeCouponKey(roundnum uint32, uid []byte) string {
left := binary.BigEndian.Uint16(uid[0:2])
right := binary.BigEndian.Uint16(uid[2:4])
multi := uint32(left) * uint32(right)
xor := roundnum ^ multi
final := make([]byte, 8)
binary.LittleEndian.PutUint32(final, xor)
copy(final[4:], uid)
return fmt.Sprintf("%s-%s-%s-%s", hex.EncodeToString(final[0:2]), hex.EncodeToString(final[2:4]), hex.EncodeToString(final[4:6]), hex.EncodeToString(final[6:8]))
}
func makeCouponCodes(name string, count int) (string, map[string]string) {
checkunique := make(map[string]bool)
keys := make(map[string]string)
uid := make([]byte, 4)
roundHash, roundnum := coupon.MakeCouponRoundHash(name)
seed := time.Now().UnixNano()
for len(keys) < count {
rand.Seed(seed)
rand.Read(uid)
code := makeCouponKey(roundnum, uid)
if _, ok := checkunique[code]; !ok {
checkunique[code] = true
keys[hex.EncodeToString(uid)] = code
}
seed = int64(binary.BigEndian.Uint32(uid))
}
return roundHash, keys
}
func generateCoupons(mongoClient gocommon.MongoClient, w http.ResponseWriter, r *http.Request) {
name, _ := gocommon.ReadStringFormValue(r.Form, "name")
effect, _ := gocommon.ReadStringFormValue(r.Form, "effect")
count, _ := gocommon.ReadIntegerFormValue(r.Form, "count")
desc, _ := gocommon.ReadStringFormValue(r.Form, "desc")
if count == 0 {
logger.Println("[generateCoupons] count == 0")
w.WriteHeader(http.StatusBadRequest)
return
}
roundHash, _ := coupon.MakeCouponRoundHash(name)
roundObj, _ := primitive.ObjectIDFromHex(roundHash + roundHash + roundHash)
if count < 0 {
// 무한 쿠폰이므로 그냥 문서 생성해 주고 끝
if _, _, err := mongoClient.Update(CollectionCoupon, bson.M{
"_id": roundObj,
}, bson.M{
"$set": &couponDoc{
Name: name,
Effect: effect,
Desc: desc,
Total: -1,
},
}, options.Update().SetUpsert(true)); err != nil {
logger.Println("[generateCoupons] Update failed :", err)
w.WriteHeader(http.StatusInternalServerError)
}
return
}
// effect가 비어있으면 기존의 roundName에 갯수를 추가해 준다
// effect가 비어있지 않으면 roundName이 겹쳐서는 안된다.
coupondoc, err := mongoClient.FindOne(CollectionCoupon, bson.M{"_id": roundObj})
if err != nil {
logger.Println("[generateCoupons] FindOne failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
lastKeys := make(map[string]bool)
if coupondoc != nil {
if r, ok := coupondoc["remains"]; ok {
remains := r.(primitive.A)
for _, uid := range remains {
lastKeys[uid.(string)] = true
}
}
}
issuedKeys := make(map[string]string)
for len(issuedKeys) < int(count) {
_, vs := makeCouponCodes(name, int(count)-len(issuedKeys))
for k, v := range vs {
if _, ok := lastKeys[k]; !ok {
// 기존 키와 중복되지 않는 것만
issuedKeys[k] = v
}
}
}
var coupons []string
var uids []string
for uid, code := range issuedKeys {
uids = append(uids, uid)
coupons = append(coupons, code)
}
if coupondoc != nil {
_, _, err = mongoClient.Update(CollectionCoupon, bson.M{
"_id": roundObj,
}, bson.M{
"$push": bson.M{"remains": bson.M{"$each": uids}},
"$inc": bson.M{"total": count},
}, options.Update().SetUpsert(true))
} else {
_, _, err = mongoClient.Update(CollectionCoupon, bson.M{
"_id": roundObj,
}, bson.M{
"$push": bson.M{"remains": bson.M{"$each": uids}},
"$set": couponDoc{
Name: name,
Effect: effect,
Desc: desc,
Total: count,
},
}, options.Update().SetUpsert(true))
}
if err != nil {
logger.Println("[generateCoupons] Update failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
enc := json.NewEncoder(w)
enc.Encode(coupons)
}
func downloadCoupons(mongoClient gocommon.MongoClient, w http.ResponseWriter, r *http.Request) {
name, _ := gocommon.ReadStringFormValue(r.Form, "name")
if len(name) == 0 {
logger.Println("[downloadCoupons] name is empty")
w.WriteHeader(http.StatusBadRequest)
return
}
round, _ := coupon.MakeCouponRoundHash(name)
roundObj, err := primitive.ObjectIDFromHex(round + round + round)
if err != nil {
// 유효하지 않은 형식의 code
logger.Println("[downloadCoupons] ObjectIDFromHex failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
var coupon couponDoc
if err := mongoClient.FindOneAs(CollectionCoupon, bson.M{
"_id": roundObj,
}, &coupon, options.FindOne().SetProjection(bson.M{"_id": 0, "remains": 1})); err != nil {
logger.Println("[downloadCoupons] FindOne failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
roundnum := binary.BigEndian.Uint32(roundObj[:])
var coupons []string
for _, uid := range coupon.Remains {
coupons = append(coupons, makeCouponKey(roundnum, []byte(uid)))
}
enc := json.NewEncoder(w)
enc.Encode(coupons)
}
func queryCoupon(mongoClient gocommon.MongoClient, w http.ResponseWriter, r *http.Request) {
code, _ := gocommon.ReadStringFormValue(r.Form, "code")
if len(code) == 0 {
logger.Println("[queryCoupon] code is empty")
w.WriteHeader(http.StatusBadRequest)
return
}
round, _ := coupon.DisolveCouponCode(code)
if len(round) == 0 {
// 유효하지 않은 형식의 code
// 쿠폰 이름일 수 있으므로 round hash를 계산한다.
round, _ = coupon.MakeCouponRoundHash(code)
}
roundObj, err := primitive.ObjectIDFromHex(round + round + round)
if err != nil {
// 유효하지 않은 형식의 code
logger.Println("[queryCoupon] ObjectIDFromHex failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
var coupon couponDoc
if err := mongoClient.FindOneAs(CollectionCoupon, bson.M{
"_id": roundObj,
}, &coupon, options.FindOne().SetProjection(bson.M{"effect": 1, "name": 1, "reason": 1, "total": 1, "desc": 1}).SetReturnKey(false)); err != nil {
logger.Println("[queryCoupon] FindOneAs failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
enc := json.NewEncoder(w)
enc.Encode(coupon)
}
func listAllCouponNames(mongoClient gocommon.MongoClient, w http.ResponseWriter, r *http.Request) {
all, err := mongoClient.FindAll(CollectionCoupon, bson.M{}, options.Find().SetProjection(bson.M{"name": 1}))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
var names []string
for _, doc := range all {
names = append(names, doc["name"].(string))
}
enc := json.NewEncoder(w)
enc.Encode(names)
}
func useCoupon(mongoClient gocommon.MongoClient, w http.ResponseWriter, r *http.Request) {
acc, ok := gocommon.ReadObjectIDFormValue(r.Form, "accid")
if !ok || acc.IsZero() {
w.WriteHeader(http.StatusBadRequest)
return
}
code, _ := gocommon.ReadStringFormValue(r.Form, "code")
code = strings.TrimSpace(code)
if len(code) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
round, key := coupon.DisolveCouponCode(code)
if len(round) == 0 {
// couponId가 쿠폰 이름일 수도 있다. 무한 쿠폰
round, _ = coupon.MakeCouponRoundHash(code)
}
// 1. 내가 이 라운드의 쿠폰을 쓴 적이 있나
already, err := mongoClient.Exists(CollectionCouponUse, bson.M{
"_id": acc,
"rounds": round,
})
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if already {
// 이미 이 라운드의 쿠폰을 사용한 적이 있다.
w.WriteHeader(http.StatusConflict)
return
}
var coupon couponDoc
roundObj, _ := primitive.ObjectIDFromHex(round + round + round)
if len(key) == 0 {
// 무한 쿠폰일 수 있으므로 존재하는지 확인
if err := mongoClient.FindOneAs(CollectionCoupon, bson.M{
"_id": roundObj,
}, &coupon, options.FindOne().SetProjection(bson.M{"_id": 0, "effect": 1, "name": 1, "reason": 1, "total": 1})); err != nil {
logger.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if coupon.Total > 0 {
// 무한 쿠폰 아니네?
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
// 2. 쿠폰을 하나 꺼냄
matched, _, err := mongoClient.Update(CollectionCoupon, bson.M{
"_id": roundObj,
}, bson.M{
"$pull": bson.M{"remains": key},
})
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if !matched {
// 쿠폰이 없다.
w.WriteHeader(http.StatusBadRequest)
return
}
// 3. round의 효과 읽기
if err := mongoClient.FindOneAndUpdateAs(CollectionCoupon, bson.M{
"_id": roundObj,
}, bson.M{
"$push": bson.M{"used": key},
}, &coupon, options.FindOneAndUpdate().SetProjection(bson.M{"effect": 1})); err != nil {
logger.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
if len(coupon.Effect) == 0 {
// 쿠폰이 없네?
w.WriteHeader(http.StatusBadRequest)
return
}
// 4. 쿠폰은 사용한 것으로 표시
// 이제 이 아래에서 실패하면 이 쿠폰은 못쓴다.
updated, _, err := mongoClient.Update(CollectionCouponUse, bson.M{
"_id": acc,
}, bson.M{
"$push": bson.M{"rounds": round},
"$set": bson.M{round + ".id": code},
"$currentDate": bson.M{round + ".ts": true},
}, options.Update().SetUpsert(true))
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if !updated {
logger.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte(coupon.Effect))
}

39
core/api_test.go Normal file
View File

@ -0,0 +1,39 @@
package core
import (
"context"
"fmt"
"testing"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"repositories.action2quare.com/ayo/gocommon"
)
func TestMakeLocalUniqueId(t *testing.T) {
ts := int64(1690815600)
start := primitive.NewDateTimeFromTime(time.Unix(ts, 0))
ts = int64(1693493999)
end := primitive.NewDateTimeFromTime(time.Unix(ts, 0))
fmt.Println(start.Time().Format(time.RFC3339))
fmt.Println(end.Time().Format(time.RFC3339))
mongoClient, err := gocommon.NewMongoClient(context.Background(), "mongodb://121.134.91.160:27018/mountain-maingate?replicaSet=rs0&retrywrites=true", "maingate")
if err != nil {
t.Error(err)
}
bi := blockinfo{
Start: start,
End: end,
Reason: "test",
}
mongoClient.Update(CollectionBlock, bson.M{
"_id": primitive.NewObjectID(),
}, bson.M{
"$set": &bi,
}, options.Update().SetUpsert(true))
}

View File

@ -13,8 +13,10 @@ import (
"net" "net"
"net/http" "net/http"
"os" "os"
"runtime/debug"
"strings" "strings"
"sync/atomic" "sync/atomic"
"text/template"
"time" "time"
"unsafe" "unsafe"
@ -122,6 +124,7 @@ type maingateConfig struct {
Mongo string `json:"maingate_mongodb_url"` Mongo string `json:"maingate_mongodb_url"`
SessionTTL int64 `json:"maingate_session_ttl"` SessionTTL int64 `json:"maingate_session_ttl"`
Autologin_ttl int64 `json:"autologin_ttl"` Autologin_ttl int64 `json:"autologin_ttl"`
AccDelTTL int64 `json:"acc_del_ttl"`
MaximumNumLinkAccount int64 `json:"maximum_num_link_account"` MaximumNumLinkAccount int64 `json:"maximum_num_link_account"`
RedirectBaseUrl string `json:"redirect_base_url"` RedirectBaseUrl string `json:"redirect_base_url"`
GoogleClientId string `json:"google_client_id"` GoogleClientId string `json:"google_client_id"`
@ -142,6 +145,18 @@ type maingateConfig struct {
FirebaseAdminSDKCredentialFile string `json:"firebase_admin_sdk_credentialfile"` FirebaseAdminSDKCredentialFile string `json:"firebase_admin_sdk_credentialfile"`
SteamAppId string `json:"steam_app_id"` SteamAppId string `json:"steam_app_id"`
SteamPublisherAuthKey string `json:"steam_publisher_authkey"` SteamPublisherAuthKey string `json:"steam_publisher_authkey"`
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 { type globalAdmins struct {
@ -169,7 +184,8 @@ type Maingate struct {
//services servicelist //services servicelist
serviceptr unsafe.Pointer serviceptr unsafe.Pointer
admins unsafe.Pointer admins unsafe.Pointer
wl whitelist wl memberContainerPtr[string, *whitelistmember]
bl memberContainerPtr[primitive.ObjectID, *blockinfo]
tokenEndpoints map[string]string tokenEndpoints map[string]string
authorizationEndpoints map[string]string authorizationEndpoints map[string]string
@ -206,7 +222,6 @@ func New(ctx context.Context) (*Maingate, error) {
err := mg.prepare(ctx) err := mg.prepare(ctx)
if err != nil { if err != nil {
logger.Error("mg.prepare() failed :", err)
return nil, err return nil, err
} }
@ -286,106 +301,107 @@ func (mg *Maingate) discoverOpenIdConfiguration(name string, url string) error {
} }
func makeErrorWithStack(err error) error {
return fmt.Errorf("%s\n%s", err.Error(), string(debug.Stack()))
}
func (mg *Maingate) prepare(context context.Context) (err error) { 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 { if err := mg.discoverOpenIdConfiguration(AuthPlatformMicrosoft, "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"); err != nil {
return err return makeErrorWithStack(err)
} }
if err := mg.discoverOpenIdConfiguration("google", "https://accounts.google.com/.well-known/openid-configuration"); err != nil { if err := mg.discoverOpenIdConfiguration("google", "https://accounts.google.com/.well-known/openid-configuration"); err != nil {
return err return makeErrorWithStack(err)
} }
// redis에서 env를 가져온 후에 // redis에서 env를 가져온 후에
mg.mongoClient, err = gocommon.NewMongoClient(context, mg.Mongo, "maingate") mg.mongoClient, err = gocommon.NewMongoClient(context, mg.Mongo, "maingate")
if err != nil { if err != nil {
return makeErrorWithStack(err)
}
if err = mg.mongoClient.MakeUniqueIndices(CollectionCouponUse, map[string]bson.D{
"idrounds": {{Key: "_id", Value: 1}, {Key: "rounds", Value: 1}},
}); err != nil {
return err return err
} }
if err = mg.mongoClient.MakeUniqueIndices(CollectionAuth, map[string]bson.D{ if err = mg.mongoClient.MakeUniqueIndices(CollectionAuth, map[string]bson.D{
"skonly": {{Key: "sk", Value: 1}}, "skonly": {{Key: "sk", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{ if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{
"platformuid": {{Key: "platform", Value: 1}, {Key: "uid", Value: 1}}, "platformuid": {{Key: "platform", Value: 1}, {Key: "uid", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{ if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{
"emailplatform": {{Key: "email", Value: 1}, {Key: "platform", Value: 1}}, "emailplatform": {{Key: "email", Value: 1}, {Key: "platform", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
}
if err = mg.mongoClient.MakeIndices(CollectionWhitelist, map[string]bson.D{
"service": {{Key: "service", Value: 1}},
}); err != nil {
return err
}
if err = mg.mongoClient.MakeIndices(CollectionFile, map[string]bson.D{
"service": {{Key: "service", Value: 1}},
}); err != nil {
return err
} }
if err = mg.mongoClient.MakeIndices(CollectionAccount, map[string]bson.D{ if err = mg.mongoClient.MakeIndices(CollectionAccount, map[string]bson.D{
"accid": {{Key: "accid", Value: 1}}, "accid": {{Key: "accid", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeUniqueIndices(CollectionFile, map[string]bson.D{ if err = mg.mongoClient.MakeUniqueIndices(CollectionFile, map[string]bson.D{
"sk": {{Key: "service", Value: 1}, {Key: "key", Value: 1}}, "keyonly": {{Key: "key", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeExpireIndex(CollectionAccount, int32(mg.AccDelTTL)); err != nil {
return makeErrorWithStack(err)
}
if err = mg.mongoClient.MakeExpireIndex(CollectionLink, int32(mg.AccDelTTL)); err != nil {
return makeErrorWithStack(err)
}
// Delete대신 _ts로 expire시킴. pipeline에 삭제 알려주기 위함
if err = mg.mongoClient.MakeExpireIndex(CollectionWhitelist, 10); err != nil { if err = mg.mongoClient.MakeExpireIndex(CollectionWhitelist, 10); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeExpireIndex(CollectionAuth, int32(mg.SessionTTL+300)); err != nil { if err = mg.mongoClient.MakeExpireIndex(CollectionAuth, int32(mg.SessionTTL+300)); err != nil {
return err return makeErrorWithStack(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 { if err = mg.mongoClient.MakeExpireIndex(CollectionBlock, int32(3)); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeUniqueIndices(CollectionPlatformLoginToken, map[string]bson.D{ if err = mg.mongoClient.MakeUniqueIndices(CollectionPlatformLoginToken, map[string]bson.D{
"platformauthtoken": {{Key: "platform", Value: 1}, {Key: "key", Value: 1}}, "platformauthtoken": {{Key: "platform", Value: 1}, {Key: "key", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeExpireIndex(CollectionPlatformLoginToken, int32(mg.SessionTTL+300)); err != nil { if err = mg.mongoClient.MakeExpireIndex(CollectionPlatformLoginToken, int32(mg.SessionTTL+300)); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeUniqueIndices(CollectionUserToken, map[string]bson.D{ if err = mg.mongoClient.MakeUniqueIndices(CollectionUserToken, map[string]bson.D{
"platformusertoken": {{Key: "platform", Value: 1}, {Key: "userid", Value: 1}}, "platformusertoken": {{Key: "platform", Value: 1}, {Key: "userid", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeUniqueIndices(CollectionGamepotUserInfo, map[string]bson.D{ if err = mg.mongoClient.MakeUniqueIndices(CollectionGamepotUserInfo, map[string]bson.D{
"gamepotuserid": {{Key: "gamepotuserid", Value: 1}}, "gamepotuserid": {{Key: "gamepotuserid", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
} }
if err = mg.mongoClient.MakeUniqueIndices(CollectionFirebaseUserInfo, map[string]bson.D{ if err = mg.mongoClient.MakeUniqueIndices(CollectionFirebaseUserInfo, map[string]bson.D{
"firebaseuserid": {{Key: "firebaseuserid", Value: 1}}, "firebaseuserid": {{Key: "firebaseuserid", Value: 1}},
}); err != nil { }); err != nil {
return err return makeErrorWithStack(err)
} }
mg.auths = makeAuthCollection(mg.mongoClient, time.Duration(mg.SessionTTL*int64(time.Second))) mg.auths = makeAuthCollection(mg.mongoClient, time.Duration(mg.SessionTTL*int64(time.Second)))
@ -397,7 +413,7 @@ func (mg *Maingate) prepare(context context.Context) (err error) {
if err = mg.mongoClient.FindAllAs(CollectionFile, nil, &preall, options.Find().SetProjection(bson.M{ if err = mg.mongoClient.FindAllAs(CollectionFile, nil, &preall, options.Find().SetProjection(bson.M{
"link": 1, "link": 1,
})); err != nil { })); err != nil {
return err return makeErrorWithStack(err)
} }
for _, pre := range preall { for _, pre := range preall {
@ -412,35 +428,33 @@ func (mg *Maingate) prepare(context context.Context) (err error) {
"_id": pre.Id, "_id": pre.Id,
}, &fulldoc) }, &fulldoc)
if err != nil { if err != nil {
return err return makeErrorWithStack(err)
} }
err = fulldoc.Save() err = fulldoc.Save()
if err != nil { if err != nil {
return err return makeErrorWithStack(err)
} }
} }
var whites []whitelistmember var whites []*whitelistmember
if err := mg.mongoClient.AllAs(CollectionWhitelist, &whites, options.Find().SetReturnKey(false)); err != nil { if err := mg.mongoClient.AllAs(CollectionWhitelist, &whites, options.Find().SetReturnKey(false)); err != nil {
return err return makeErrorWithStack(err)
} }
mg.wl.init(whites) mg.wl.init(whites)
var blocks []*blockinfo
if err := mg.mongoClient.AllAs(CollectionBlock, &blocks); err != nil {
return makeErrorWithStack(err)
}
mg.bl.init(blocks)
go watchAuthCollection(context, mg.auths, mg.mongoClient) go watchAuthCollection(context, mg.auths, mg.mongoClient)
go mg.watchWhitelistCollection(context) go mg.wl.watchCollection(context, CollectionWhitelist, mg.mongoClient)
go mg.bl.watchCollection(context, CollectionBlock, mg.mongoClient)
return nil return nil
} }
func whitelistKey(email string, platform string) string {
if strings.HasPrefix(email, "*@") {
// 도메인 전체 허용
return email[2:]
}
return email
}
func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error { func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
var allServices []*serviceDescription var allServices []*serviceDescription
if err := mg.mongoClient.AllAs(CollectionService, &allServices, options.Find().SetReturnKey(false)); err != nil { if err := mg.mongoClient.AllAs(CollectionService, &allServices, options.Find().SetReturnKey(false)); err != nil {
@ -479,6 +493,7 @@ func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMu
DivisionForUser: DivisionForUser{ DivisionForUser: DivisionForUser{
Priority: 0, Priority: 0,
State: DivisionState_FullOpen, State: DivisionState_FullOpen,
LockCreateChar: false,
}, },
Url: fmt.Sprintf("http://%s/warehouse", ipaddr), Url: fmt.Sprintf("http://%s/warehouse", ipaddr),
@ -555,6 +570,14 @@ func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMu
serveMux.Handle(pattern, http.StripPrefix(pattern, staticfs)) serveMux.Handle(pattern, http.StripPrefix(pattern, staticfs))
logger.Println("maingate static registered :", pattern) 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, "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", AuthPlatformGoogle), mg.platform_google_authorize)
serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformGoogle), mg.platform_google_authorize_result) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformGoogle), mg.platform_google_authorize_result)
@ -787,14 +810,19 @@ func (mg *Maingate) getProviderInfo(platform string, uid string) (string, string
if provider == "" || providerid == "" { if provider == "" || providerid == "" {
return "", "", errors.New("getProviderInfo - firebase info not found: " + provider + " / " + providerid) return "", "", errors.New("getProviderInfo - firebase info not found: " + provider + " / " + providerid)
} }
case "":
//guest auth
providerid = uid
if providerid == "" {
return "", "", errors.New("getProviderInfo - guest provider id not found: " + provider + " / " + providerid)
}
default: default:
provider = platform provider = platform
providerid = uid providerid = uid
}
if provider == "" || providerid == "" { if provider == "" || providerid == "" {
return "", "", errors.New("getProviderInfo - provider info not found: " + provider + " / " + providerid) return "", "", errors.New("getProviderInfo - provider info not found: " + provider + " / " + providerid)
} }
}
return provider, providerid, nil return provider, providerid, nil
@ -956,3 +984,31 @@ func JWTparseCode(keyurl string, code string) (string, string, string) {
//--- nonce 체크 필요하다. //--- nonce 체크 필요하다.
return claims["sub"].(string), email, nonce return claims["sub"].(string), email, nonce
} }
func (mg *Maingate) google_analytics_html(w http.ResponseWriter, r *http.Request) {
parsedTemplate, _ := template.ParseFiles("template/track-event.html")
err := parsedTemplate.Execute(w, nil)
if err != nil {
logger.Error("Error executing template :", err)
return
}
}
func (mg *Maingate) google_analytics_js(w http.ResponseWriter, r *http.Request) {
fgaconfig := Firebase_Google_Analytics_JS_SDK_Config{
FGA_apiKey: mg.FGA_apiKey,
FGA_authDomain: mg.FGA_authDomain,
FGA_databaseURL: mg.FGA_databaseURL,
FGA_projectId: mg.FGA_projectId,
FGA_storageBucket: mg.FGA_storageBucket,
FGA_messagingSenderId: mg.FGA_messagingSenderId,
FGA_appId: mg.FGA_appId,
FGA_measurementId: mg.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
}
}

177
core/member_container.go Normal file
View File

@ -0,0 +1,177 @@
package core
import (
"context"
"sync/atomic"
"time"
"unsafe"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
)
type memberContraints[K comparable] interface {
Key() K
Expired() bool
}
type memberContainerPtr[K comparable, T memberContraints[K]] struct {
ptr unsafe.Pointer
}
func (p *memberContainerPtr[K, T]) init(ms []T) {
next := map[K]T{}
for _, m := range ms {
next[m.Key()] = m
}
atomic.StorePointer(&p.ptr, unsafe.Pointer(&next))
}
func (p *memberContainerPtr[K, T]) add(m T) {
ptr := atomic.LoadPointer(&p.ptr)
src := (*map[K]T)(ptr)
next := map[K]T{}
for k, v := range *src {
next[k] = v
}
next[m.Key()] = m
atomic.StorePointer(&p.ptr, unsafe.Pointer(&next))
}
func (p *memberContainerPtr[K, T]) get(key K) (T, bool) {
ptr := atomic.LoadPointer(&p.ptr)
src := (*map[K]T)(ptr)
out, found := (*src)[key]
return out, found
}
func (p *memberContainerPtr[K, T]) remove(key K) {
ptr := atomic.LoadPointer(&p.ptr)
src := (*map[K]T)(ptr)
next := map[K]T{}
for k, v := range *src {
next[k] = v
}
delete(next, key)
atomic.StorePointer(&p.ptr, unsafe.Pointer(&next))
}
type memberPipelineDocument[K comparable, T memberContraints[K]] struct {
OperationType string `bson:"operationType"`
DocumentKey struct {
Id primitive.ObjectID `bson:"_id"`
} `bson:"documentKey"`
Member T `bson:"fullDocument"`
}
func (p *memberContainerPtr[K, T]) all() []T {
ptr := atomic.LoadPointer(&p.ptr)
src := (*map[K]T)(ptr)
out := make([]T, 0, len(*src))
for _, m := range *src {
if m.Expired() {
continue
}
out = append(out, m)
}
return out
}
func (p *memberContainerPtr[K, T]) contains(key K, out *T) bool {
ptr := atomic.LoadPointer(&p.ptr)
src := (*map[K]T)(ptr)
found, exists := (*src)[key]
if exists {
if found.Expired() {
p.remove(key)
return false
}
if out != nil {
*out = found
}
return true
}
return false
}
func (p *memberContainerPtr[K, T]) watchCollection(parentctx context.Context, coll gocommon.CollectionName, mc gocommon.MongoClient) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
matchStage := bson.D{
{
Key: "$match", Value: bson.D{
{Key: "operationType", Value: bson.D{
{Key: "$in", Value: bson.A{
"update",
"insert",
}},
}},
},
}}
projectStage := bson.D{
{
Key: "$project", Value: bson.D{
{Key: "documentKey", Value: 1},
{Key: "fullDocument", Value: 1},
},
},
}
var stream *mongo.ChangeStream
var err error
var ctx context.Context
for {
if stream == nil {
stream, err = mc.Watch(coll, mongo.Pipeline{matchStage, projectStage})
if err != nil {
logger.Error("watchCollection watch failed :", err)
time.Sleep(time.Minute)
continue
}
ctx = context.TODO()
}
changed := stream.TryNext(ctx)
if ctx.Err() != nil {
logger.Error("watchCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
break
}
if changed {
var data memberPipelineDocument[K, T]
if err := stream.Decode(&data); err == nil {
p.add(data.Member)
} else {
logger.Error("watchCollection stream.Decode failed :", err)
}
} else if stream.Err() != nil || stream.ID() == 0 {
select {
case <-ctx.Done():
logger.Println("watchCollection is done")
stream.Close(ctx)
return
case <-time.After(time.Second):
logger.Error("watchCollection stream error :", stream.Err())
stream.Close(ctx)
stream = nil
}
} else {
time.Sleep(time.Second)
}
}
}

View File

@ -39,10 +39,11 @@ func (mg *Maingate) platform_steamsdk_authorize(w http.ResponseWriter, r *http.R
return return
} }
if !*noauth {
err = authenticateSteamUser(mg.SteamPublisherAuthKey, mg.SteamAppId, authinfo.UserSteamId, authinfo.UserAuthToken) err = authenticateSteamUser(mg.SteamPublisherAuthKey, mg.SteamAppId, authinfo.UserSteamId, authinfo.UserAuthToken)
}
if err == nil { if err == nil {
acceestoken_expire_time := time.Date(2999, 1, int(time.January), 0, 0, 0, 0, time.UTC).Unix() acceestoken_expire_time := time.Date(2999, 1, int(time.January), 0, 0, 0, 0, time.UTC).Unix()
var info usertokeninfo var info usertokeninfo

View File

@ -7,10 +7,9 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strconv"
"strings" "strings"
"sync/atomic"
"time" "time"
"unsafe"
"repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
@ -22,19 +21,45 @@ import (
type blockinfo struct { type blockinfo struct {
Start primitive.DateTime `bson:"start" json:"start"` Start primitive.DateTime `bson:"start" json:"start"`
End primitive.DateTime `bson:"_ts"` End primitive.DateTime `bson:"_ts" json:"_ts"`
Reason string `bson:"reason" json:"reason"` 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 { type whitelistmember struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
Email string `bson:"email" json:"email"` Email string `bson:"email" json:"email"`
Platform string `bson:"platform" json:"platform"` Platform string `bson:"platform" json:"platform"`
Desc string `bson:"desc" json:"desc"` Desc string `bson:"desc" json:"desc"`
Expired primitive.DateTime `bson:"_ts,omitempty" json:"_ts,omitempty"` ExpiredAt primitive.DateTime `bson:"_ts,omitempty" json:"_ts,omitempty"`
} }
type whitelist struct { func (wh *whitelistmember) Key() string {
emailptr unsafe.Pointer 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 { type usertokeninfo struct {
@ -47,54 +72,6 @@ type usertokeninfo struct {
accesstoken_expire_time int64 // microsoft only accesstoken_expire_time int64 // microsoft only
} }
func (wl *whitelist) init(total []whitelistmember) {
all := make(map[string]*whitelistmember)
for _, member := range total {
all[whitelistKey(member.Email, member.Platform)] = &member
}
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&all))
}
func addToUnsafePointer(to *unsafe.Pointer, m *whitelistmember) {
ptr := atomic.LoadPointer(to)
src := (*map[string]*whitelistmember)(ptr)
next := map[string]*whitelistmember{}
for k, v := range *src {
next[k] = v
}
next[whitelistKey(m.Email, m.Platform)] = m
atomic.StorePointer(to, unsafe.Pointer(&next))
}
func removeFromUnsafePointer(from *unsafe.Pointer, email string, platform string) {
ptr := atomic.LoadPointer(from)
src := (*map[string]*whitelistmember)(ptr)
next := make(map[string]*whitelistmember)
for k, v := range *src {
next[k] = v
}
delete(next, whitelistKey(email, platform))
atomic.StorePointer(from, unsafe.Pointer(&next))
}
func (wl *whitelist) add(m *whitelistmember) {
addToUnsafePointer(&wl.emailptr, m)
}
func (wl *whitelist) remove(email string, platform string) {
removeFromUnsafePointer(&wl.emailptr, email, platform)
}
func (wl *whitelist) isMember(email string, platform string) bool {
ptr := atomic.LoadPointer(&wl.emailptr)
src := *(*map[string]*whitelistmember)(ptr)
_, exists := src[whitelistKey(email, platform)]
return exists
}
type DivisionStateName string type DivisionStateName string
const ( const (
@ -113,6 +90,7 @@ type Maintenance struct {
type DivisionForUser struct { type DivisionForUser struct {
Priority int `bson:"priority" json:"priority"` Priority int `bson:"priority" json:"priority"`
State DivisionStateName `bson:"state" json:"state"` State DivisionStateName `bson:"state" json:"state"`
LockCreateChar bool `bson:"lockcreatechar" json:"lockcreatechar"`
Maintenance *Maintenance `bson:"maintenance,omitempty" json:"maintenance,omitempty"` Maintenance *Maintenance `bson:"maintenance,omitempty" json:"maintenance,omitempty"`
} }
@ -134,7 +112,8 @@ type serviceDescription struct {
VersionSplits map[string]string `bson:"version_splits" json:"version_splits"` VersionSplits map[string]string `bson:"version_splits" json:"version_splits"`
auths *gocommon.AuthCollection auths *gocommon.AuthCollection
wl *whitelist wl *memberContainerPtr[string, *whitelistmember]
bl *memberContainerPtr[primitive.ObjectID, *blockinfo]
mongoClient gocommon.MongoClient mongoClient gocommon.MongoClient
sessionTTL time.Duration sessionTTL time.Duration
@ -144,10 +123,8 @@ type serviceDescription struct {
updateUserinfo func(info usertokeninfo) (bool, string, string) updateUserinfo func(info usertokeninfo) (bool, string, string)
getProviderInfo func(platform string, uid string) (string, string, error) getProviderInfo func(platform string, uid string) (string, string, error)
divisionsForUsersSerialized []byte
divisionsSerialized []byte divisionsSerialized []byte
serviceSerialized []byte serviceSerialized []byte
serviceSummarySerialized []byte
divisionsSplits map[string][]byte divisionsSplits map[string][]byte
} }
@ -210,11 +187,10 @@ func (sh *serviceDescription) prepare(mg *Maingate) error {
} }
divsForUsers := make(map[string]*DivisionForUser) divsForUsers := make(map[string]*DivisionForUser)
var namesOnly []string defaultDivNames := make(map[string]bool)
for dn, div := range divs { for dn, div := range divs {
namesOnly = append(namesOnly, dn) if div.State != DivisionState_Closed {
if div.State == DivisionState_Closed { defaultDivNames[dn] = true
continue
} }
divsForUsers[dn] = &div.DivisionForUser divsForUsers[dn] = &div.DivisionForUser
@ -251,25 +227,30 @@ func (sh *serviceDescription) prepare(mg *Maingate) error {
} }
sh.divisionsSerialized, _ = json.Marshal(divs) sh.divisionsSerialized, _ = json.Marshal(divs)
sh.divisionsForUsersSerialized, _ = json.Marshal(divsForUsers)
if len(sh.VersionSplits) == 0 {
sh.VersionSplits = map[string]string{
"default": strings.Join(namesOnly, ","),
}
}
sh.divisionsSplits = make(map[string][]byte) sh.divisionsSplits = make(map[string][]byte)
for ver, divnamesT := range sh.VersionSplits { for ver, divnamesT := range sh.VersionSplits {
if ver == "default" {
continue
}
divnames := strings.Split(divnamesT, ",") divnames := strings.Split(divnamesT, ",")
split := make(map[string]*DivisionForUser) split := make(map[string]*DivisionForUser)
for _, divname := range divnames { for _, divname := range divnames {
split[divname] = divsForUsers[divname] split[divname] = divsForUsers[divname]
// 스플릿 된 버전은 default에서 제거해야 한다.
delete(defaultDivNames, divname)
} }
splitMarshaled, _ := json.Marshal(split) splitMarshaled, _ := json.Marshal(split)
sh.divisionsSplits[ver] = splitMarshaled 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.MaximumNumLinkAccount = mg.maingateConfig.MaximumNumLinkAccount
sh.mongoClient = mg.mongoClient sh.mongoClient = mg.mongoClient
sh.auths = mg.auths sh.auths = mg.auths
@ -281,9 +262,10 @@ func (sh *serviceDescription) prepare(mg *Maingate) error {
sh.getProviderInfo = mg.getProviderInfo sh.getProviderInfo = mg.getProviderInfo
sh.wl = &mg.wl sh.wl = &mg.wl
sh.serviceSummarySerialized, _ = json.Marshal(sh.ServiceDescriptionSummary) sh.bl = &mg.bl
sh.serviceSerialized, _ = json.Marshal(sh)
logger.Println("service is ready :", sh.ServiceCode, string(sh.divisionsSerialized)) logger.Println("service is ready :", sh.ServiceCode, string(sh.serviceSerialized))
return nil return nil
} }
@ -329,13 +311,6 @@ func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
// fmt.Println(oldAuth.Uid) // fmt.Println(oldAuth.Uid)
// fmt.Println("=================") // fmt.Println("=================")
//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
}
bfinfo, err := sh.getUserBrowserInfo(r) bfinfo, err := sh.getUserBrowserInfo(r)
if err != nil { if err != nil {
logger.Error("getUserBrowserInfo failed :", err) logger.Error("getUserBrowserInfo failed :", err)
@ -347,7 +322,7 @@ func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
if !guestlink { if !guestlink {
_, err = sh.readProfile(oldType, oldId, bfinfo) _, err = sh.readProfile(oldType, oldId, bfinfo)
if err != nil { if err != nil {
logger.Error("readProfile(old) failed :", err) logger.Println("readProfile(old) failed :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
@ -355,9 +330,23 @@ func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
logger.Println("from guest acc to real acc link : ", oldId, bfinfo, newType, newId, bfinfo) 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) email, err := sh.readProfile(newType, newId, bfinfo)
if err != nil { if err != nil {
logger.Error("readProfile(new) failed :", err) logger.Println("readProfile(new) failed :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
@ -370,8 +359,9 @@ func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
newType, newId, err = sh.getProviderInfo(newType, newId) newType, newId, err = sh.getProviderInfo(newType, newId)
if err != nil { if err != nil {
logger.Error("getProviderInfo failed :", err) logger.Println("getProviderInfo failed :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return
} }
createtime := primitive.NewDateTimeFromTime(time.Now().UTC()) createtime := primitive.NewDateTimeFromTime(time.Now().UTC())
@ -400,7 +390,7 @@ func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
}, options.Update().SetUpsert(true)) }, options.Update().SetUpsert(true))
if err != nil { if err != nil {
logger.Error("link failed. Update ServiceName err :", err) logger.Error("link failed. Update ServiceName err :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -412,6 +402,20 @@ func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
return 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()) logger.Println("link success :", r.URL.Query())
} }
@ -451,6 +455,13 @@ func (sh *serviceDescription) unlink(w http.ResponseWriter, r *http.Request) {
// fmt.Println(authInfo.Uid) // fmt.Println(authInfo.Uid)
// fmt.Println("=================") // 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 { if authInfo.Uid != sId || authInfo.Platform != sType {
logger.Println("unlink failed. session key is not correct :", *authInfo, queryvals) logger.Println("unlink failed. session key is not correct :", *authInfo, queryvals)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
@ -465,7 +476,7 @@ func (sh *serviceDescription) unlink(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
logger.Error("unlink failed, fail to count accounts :", err) logger.Error("unlink failed, fail to count accounts :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusInternalServerError)
} }
if len(accDocs) <= 1 { if len(accDocs) <= 1 {
@ -494,7 +505,7 @@ func (sh *serviceDescription) unlink(w http.ResponseWriter, r *http.Request) {
}, options.FindOneAndDelete().SetProjection(bson.M{"_id": 1})) }, options.FindOneAndDelete().SetProjection(bson.M{"_id": 1}))
if err != nil { if err != nil {
logger.Error("unlink failed. Delete ServiceName err :", err) logger.Error("unlink failed. Delete ServiceName err :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusInternalServerError)
return return
} }
if preid == nil { if preid == nil {
@ -541,6 +552,13 @@ func (sh *serviceDescription) linkinfo(w http.ResponseWriter, r *http.Request) {
// fmt.Println(authInfo.Uid) // fmt.Println(authInfo.Uid)
// fmt.Println("=================") // 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 oldAuth.Token != oldToken || oldAuth.Uid != oldId || oldAuth.Platform != oldType {
if authInfo.Uid != sId || authInfo.Platform != sType { if authInfo.Uid != sId || authInfo.Platform != sType {
logger.Println("linkinfo failed. session key is not correct :", *authInfo, queryvals) logger.Println("linkinfo failed. session key is not correct :", *authInfo, queryvals)
@ -556,7 +574,7 @@ func (sh *serviceDescription) linkinfo(w http.ResponseWriter, r *http.Request) {
})) }))
if err != nil { if err != nil {
logger.Error("linkinfo failed. CountDocuments err :", err) logger.Error("linkinfo failed. CountDocuments err :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -570,6 +588,11 @@ func (sh *serviceDescription) linkinfo(w http.ResponseWriter, r *http.Request) {
}, options.Find().SetLimit(sh.MaximumNumLinkAccount).SetProjection(bson.M{ }, options.Find().SetLimit(sh.MaximumNumLinkAccount).SetProjection(bson.M{
platformName: 1, platformName: 1,
})) }))
if err != nil {
logger.Error("linkinfo failed. FindAll returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
var linkstrs []string var linkstrs []string
for _, link := range links { for _, link := range links {
@ -579,7 +602,7 @@ func (sh *serviceDescription) linkinfo(w http.ResponseWriter, r *http.Request) {
linkbytes, err := json.Marshal(linkstrs) linkbytes, err := json.Marshal(linkstrs)
if err != nil { if err != nil {
logger.Error("linkinfo failed. json marshal fail :", err) logger.Error("linkinfo failed. json marshal fail :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -612,28 +635,29 @@ func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request)
//email, err := sh.readProfile(authtype, uid, accesstoken) //email, err := sh.readProfile(authtype, uid, accesstoken)
bfinfo, err := sh.getUserBrowserInfo(r) bfinfo, err := sh.getUserBrowserInfo(r)
if err != nil { if err != nil {
logger.Error("getUserBrowserInfo failed :", err) logger.Println("getUserBrowserInfo failed :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
email, err = sh.readProfile(authtype, uid, bfinfo) email, err = sh.readProfile(authtype, uid, bfinfo)
if err != nil { if err != nil {
logger.Error("readProfile failed :", err) logger.Println("readProfile failed :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
newType, newId, err := sh.getProviderInfo(authtype, uid) newType, newId, err := sh.getProviderInfo(authtype, uid)
if err != nil { if err != nil {
logger.Error("getProviderInfo failed :", err) logger.Println("getProviderInfo failed :", err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return
} }
if authtype != newType || uid != newId { if authtype != newType || uid != newId {
logger.Printf("auth success ( redirect ) : %s->%s, %s->%s, %s, %s", authtype, newType, uid, newId, email, session)
authtype = newType authtype = newType
uid = newId uid = newId
logger.Println("auth success ( redirect ) :", authtype, uid, email, session)
} }
} else { } else {
email = fmt.Sprintf("%s@guest.flag", uid) email = fmt.Sprintf("%s@guest.flag", uid)
@ -654,13 +678,15 @@ func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request)
"create": createtime, "create": createtime,
"email": email, "email": email,
}, },
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"_id": 1})) }, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{
"_id": 1,
"_ts": 1,
}))
if err != nil { if err != nil {
logger.Error("authorize failed :", err) logger.Error("authorize failed :", err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
linkid := link["_id"].(primitive.ObjectID) linkid := link["_id"].(primitive.ObjectID)
newaccid := primitive.NewObjectID() newaccid := primitive.NewObjectID()
for i := 0; i < len(sh.serviceCodeBytes); i++ { for i := 0; i < len(sh.serviceCodeBytes); i++ {
@ -684,28 +710,16 @@ func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request)
oldcreate := account["create"].(primitive.DateTime) oldcreate := account["create"].(primitive.DateTime)
newaccount := oldcreate == createtime newaccount := oldcreate == createtime
var bi blockinfo var bi *blockinfo
if err := sh.mongoClient.FindOneAs(CollectionBlock, bson.M{ if sh.bl.contains(accid, &bi) {
"code": sh.ServiceCode, // 블럭된 계정. 블락 정보를 알려준다.
"accid": accid, w.Header().Add("MG-ACCOUNTBLOCK-START", strconv.FormatInt(bi.Start.Time().Unix(), 10))
}, &bi); err != nil { w.Header().Add("MG-ACCOUNTBLOCK-END", strconv.FormatInt(bi.End.Time().Unix(), 10))
logger.Error("authorize failed. find blockinfo in CollectionBlock err:", err) w.Header().Add("MG-ACCOUNTBLOCK-REASON", bi.Reason)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusUnauthorized)
return return
} }
if !bi.Start.Time().IsZero() {
now := time.Now().UTC()
if bi.Start.Time().Before(now) && bi.End.Time().After(now) {
// block됐네?
// status는 정상이고 reason을 넘겨주자
json.NewEncoder(w).Encode(map[string]any{
"blocked": bi,
})
return
}
}
newsession := primitive.NewObjectID() newsession := primitive.NewObjectID()
expired := primitive.NewDateTimeFromTime(time.Now().UTC().Add(sh.sessionTTL)) expired := primitive.NewDateTimeFromTime(time.Now().UTC().Add(sh.sessionTTL))
newauth := gocommon.Authinfo{ newauth := gocommon.Authinfo{
@ -726,12 +740,25 @@ func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request)
return return
} }
logger.Println("session created :", accid, authtype, uid, email, newsession)
output := map[string]any{ output := map[string]any{
"sk": newsession.Hex(), "sk": newsession.Hex(),
"expirein": sh.sessionTTL.Seconds(), "expirein": sh.sessionTTL.Seconds(),
"newAccount": newaccount, "newAccount": newaccount,
"accid": newauth.Accid.Hex(), "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) bt, _ := json.Marshal(output)
w.Write(bt) w.Write(bt)
} else if len(session) > 0 { } else if len(session) > 0 {
@ -747,8 +774,7 @@ func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request)
}, },
}, options.Update().SetUpsert(false)) }, options.Update().SetUpsert(false))
if err != nil { if err != nil {
logger.Error("update auth collection failed") logger.Error("update auth collection failed :", err)
logger.Error(err)
return return
} }
@ -763,6 +789,9 @@ func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request)
"sk": session, "sk": session,
"expirein": sh.sessionTTL.Seconds(), "expirein": sh.sessionTTL.Seconds(),
} }
logger.Println("session updated :", authtype, uid, session)
bt, _ := json.Marshal(output) bt, _ := json.Marshal(output)
w.Write(bt) w.Write(bt)
} else { } else {
@ -805,6 +834,7 @@ func (sh *serviceDescription) delacc(w http.ResponseWriter, r *http.Request) {
sType := queryvals.Get("stype") sType := queryvals.Get("stype")
sId := queryvals.Get("sid") sId := queryvals.Get("sid")
sk := queryvals.Get("sk") sk := queryvals.Get("sk")
cancel := queryvals.Has("cancel")
authInfo := sh.auths.Find(sk) authInfo := sh.auths.Find(sk)
if authInfo == nil { if authInfo == nil {
@ -820,7 +850,7 @@ func (sh *serviceDescription) delacc(w http.ResponseWriter, r *http.Request) {
return return
} }
accids, err := sh.mongoClient.FindAll(CollectionAccount, bson.M{ linkidMap, err := sh.mongoClient.FindAll(CollectionAccount, bson.M{
"accid": authInfo.Accid, "accid": authInfo.Accid,
}, options.Find().SetProjection(bson.M{ }, options.Find().SetProjection(bson.M{
"_id": 1, "_id": 1,
@ -831,27 +861,55 @@ func (sh *serviceDescription) delacc(w http.ResponseWriter, r *http.Request) {
return return
} }
var addIdFilter bson.A var linkidAry primitive.A
for _, accid := range accids { for _, linkid := range linkidMap {
addIdFilter = append(addIdFilter, accid["_id"].(primitive.ObjectID)) linkidAry = append(linkidAry, linkid["_id"].(primitive.ObjectID))
} }
delfilter := bson.D{{Key: "_id", Value: bson.D{{Key: "$in", Value: addIdFilter}}}} delfilter := primitive.M{"_id": bson.M{"$in": linkidAry}}
delaccnum, err := sh.mongoClient.DeleteMany(CollectionAccount, delfilter) var delop primitive.M
if err != nil { if !cancel {
logger.Error("delacc failed. Delete many CollectionAccount err :", err) curtime := primitive.NewDateTimeFromTime(time.Now().UTC())
w.WriteHeader(http.StatusBadRequest) delop = primitive.M{
"$set": primitive.M{"_ts": curtime},
}
if sType == 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 return
} }
_, err = sh.mongoClient.DeleteMany(CollectionLink, delfilter) delfilter = primitive.M{"_id": targetLinkId[0]["_id"].(primitive.ObjectID)}
if err != nil { delop = primitive.M{
logger.Error("delacc failed. Delete many CollectionLink err :", err) "$unset": primitive.M{"_ts": true},
w.WriteHeader(http.StatusBadRequest) }
}
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 return
} }
logger.Println("delacc success :", delaccnum) 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) { func (sh *serviceDescription) serveHTTP(w http.ResponseWriter, r *http.Request) {
@ -934,7 +992,8 @@ func (sh *serviceDescription) serveHTTP(w http.ResponseWriter, r *http.Request)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
if sh.wl.isMember(cell.ToAuthinfo().Email, cell.ToAuthinfo().Platform) { wm := &whitelistmember{Email: cell.ToAuthinfo().Email, Platform: cell.ToAuthinfo().Platform}
if sh.wl.contains(wm.Key(), nil) {
// qa 권한이면 입장 가능 // qa 권한이면 입장 가능
w.Write([]byte(fmt.Sprintf(`{"service":"%s"}`, div.Url))) w.Write([]byte(fmt.Sprintf(`{"service":"%s"}`, div.Url)))
} else if div.Maintenance != nil { } else if div.Maintenance != nil {
@ -953,7 +1012,7 @@ func (sh *serviceDescription) serveHTTP(w http.ResponseWriter, r *http.Request)
} }
} }
} else { } else {
logger.Println("div is not found :", divname) logger.Println("div is not found :", divname, sh.Divisions)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
} }
} else { } else {

View File

@ -43,102 +43,6 @@ type filePipelineDocument struct {
File *FileDocumentDesc `bson:"fullDocument"` File *FileDocumentDesc `bson:"fullDocument"`
} }
type whilelistPipelineDocument struct {
OperationType string `bson:"operationType"`
DocumentKey struct {
Id primitive.ObjectID `bson:"_id"`
} `bson:"documentKey"`
Member *whitelistmember `bson:"fullDocument"`
}
func (mg *Maingate) watchWhitelistCollection(parentctx context.Context) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
matchStage := bson.D{
{
Key: "$match", Value: bson.D{
{Key: "operationType", Value: bson.D{
{Key: "$in", Value: bson.A{
"update",
"insert",
}},
}},
},
}}
projectStage := bson.D{
{
Key: "$project", Value: bson.D{
{Key: "documentKey", Value: 1},
{Key: "operationType", Value: 1},
{Key: "fullDocument", Value: 1},
},
},
}
var stream *mongo.ChangeStream
var err error
var ctx context.Context
for {
if stream == nil {
stream, err = mg.mongoClient.Watch(CollectionWhitelist, mongo.Pipeline{matchStage, projectStage})
if err != nil {
logger.Error("watchWhitelistCollection watch failed :", err)
time.Sleep(time.Minute)
continue
}
ctx = context.TODO()
}
changed := stream.TryNext(ctx)
if ctx.Err() != nil {
logger.Error("watchWhitelistCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
break
}
if changed {
var data whilelistPipelineDocument
if err := stream.Decode(&data); err == nil {
ot := data.OperationType
switch ot {
case "insert":
// 새 화이트리스트 멤버
mg.service().wl.add(data.Member)
case "update":
if data.Member.Expired != 0 {
logger.Println("whitelist member is removed :", *data.Member)
mg.service().wl.remove(data.Member.Email, data.Member.Platform)
} else {
logger.Println("whitelist member is updated :", *data.Member)
mg.service().wl.add(data.Member)
}
}
} else {
logger.Error("watchWhitelistCollection stream.Decode failed :", err)
}
} else if stream.Err() != nil || stream.ID() == 0 {
select {
case <-ctx.Done():
logger.Println("watchWhitelistCollection is done")
stream.Close(ctx)
return
case <-time.After(time.Second):
logger.Error("watchWhitelistCollection stream error :", stream.Err())
stream.Close(ctx)
stream = nil
}
} else {
time.Sleep(time.Second)
}
}
}
func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *http.ServeMux, prefix string) { func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *http.ServeMux, prefix string) {
defer func() { defer func() {
s := recover() s := recover()

537
fba/js.js Normal file

File diff suppressed because one or more lines are too long

11
fba/track-event.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript" src="./fb-ga.min.js">
</script>
</body>
</html>
<!-- <body> -->
<!-- <body onload="window.FBA.TrackLogEvent('DESKTOP-TEST');"> -->
<!-- <script type="cjs" src="./fb-ga.rollup.js"> -->

2
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
go.mongodb.org/mongo-driver v1.11.7 go.mongodb.org/mongo-driver v1.11.7
google.golang.org/api v0.128.0 google.golang.org/api v0.128.0
repositories.action2quare.com/ayo/gocommon v0.0.0-20230801051747-b501160efc3b repositories.action2quare.com/ayo/gocommon v0.0.0-20230912075917-f9a146321cdb
) )
require ( require (

26
go.sum
View File

@ -268,27 +268,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230612013915-5950ff4bb82e h1:m0jo1r+2NtBfxwj92e6EVaBZpzTDT6Hq7D93vWO4h9Y= repositories.action2quare.com/ayo/gocommon v0.0.0-20230912075917-f9a146321cdb h1:Rdf6uhBIWunRLZ2LIT1hSovYXxZoOzx9mdSK5bjWpos=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230612013915-5950ff4bb82e/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns= repositories.action2quare.com/ayo/gocommon v0.0.0-20230912075917-f9a146321cdb/go.mod h1:rn6NA28Mej+qgLNx/Bu2wsdGyIycmacqlNP6gUXX2a0=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230614091557-9b877d9a732c h1:fhCuu0jFps8T1sN8hO0fGnatvNDW6VwM96PV26EA3T4=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230614091557-9b877d9a732c/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230616031450-0b2c9351a717 h1:WrkkEWN3bh1QAulNJZjAiwXx2aPAj39OoIyJFUXmDaE=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230616031450-0b2c9351a717/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230616032216-378bc19f3742 h1:qEbzwVDz1w2ewNHu+vipzV+a804wmwRWe+0vnhCbJr4=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230616032216-378bc19f3742/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230620005911-15ba3e93d621 h1:9Hzdn13l9U0RJn9mMXsZQr+jsmsgy3zQFsBHPSOJnxM=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230620005911-15ba3e93d621/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621051330-0d8752e66161 h1:sgixcFwdLOqcvwqTaKcsMEepXsLmNEgaybyur3QHRgk=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621051330-0d8752e66161/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22 h1:DImSGNxZrc+Q4WlS1OKMsLAScEfDYLX4XMJdjAaVnXc=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710081612-3024a17b540f h1:MMA/6fqn76zSOkUQzG8v+IbWvrtY1mXN5xF1WOZonVc=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710081612-3024a17b540f/go.mod h1:rn6NA28Mej+qgLNx/Bu2wsdGyIycmacqlNP6gUXX2a0=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710084625-dd05ebf6ceb9 h1:dizJeTf3xt7wEeq72sYqlgdy7an8kezA6JuqALKPCBQ=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710084625-dd05ebf6ceb9/go.mod h1:rn6NA28Mej+qgLNx/Bu2wsdGyIycmacqlNP6gUXX2a0=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710085335-ead6543d95f9 h1:zJ+33DKEA6CObPii5c5l5IbUIws31+Ni5NzzC3oYRLw=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710085335-ead6543d95f9/go.mod h1:rn6NA28Mej+qgLNx/Bu2wsdGyIycmacqlNP6gUXX2a0=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710085810-8173216e9574 h1:Ha0d/sv/MzC3ASCTXfe2tAFJieLNJmTCBL8aETEOY14=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710085810-8173216e9574/go.mod h1:rn6NA28Mej+qgLNx/Bu2wsdGyIycmacqlNP6gUXX2a0=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230801051747-b501160efc3b h1:yV1cBeu0GFxkDD6TDxzKv/rM3OMtyt1JXpeqDF5IO3Y=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230801051747-b501160efc3b/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=

View File

@ -10,3 +10,5 @@ go build -ldflags="-s -w" .
Compress-Archive -Path maingate -Update -DestinationPath maingate.zip Compress-Archive -Path maingate -Update -DestinationPath maingate.zip
Compress-Archive -Path *-firebase-*.json -Update -DestinationPath maingate.zip Compress-Archive -Path *-firebase-*.json -Update -DestinationPath maingate.zip
Compress-Archive -Path fba -Update -DestinationPath maingate.zip
Compress-Archive -Path template -Update -DestinationPath maingate.zip

1
template/fb-ga.min.js vendored Normal file

File diff suppressed because one or more lines are too long