Files
gocommon/authcollection.go

387 lines
9.3 KiB
Go
Raw Permalink Normal View History

package gocommon
2023-05-24 15:10:15 +09:00
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"sync/atomic"
"time"
"unsafe"
"repositories.action2quare.com/ayo/gocommon/logger"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson/primitive"
)
2023-05-28 19:07:15 +09:00
type Authinfo struct {
Accid primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
Platform string
Uid string
Email string
Sk primitive.ObjectID
RefreshToken string `bson:"refresh_token,omitempty" json:"refresh_token,omitempty"`
Expired primitive.DateTime `bson:"_ts" json:"_ts"`
}
2023-05-24 15:10:15 +09:00
const (
Squashed commit of the following: commit 29b2f258507d9e11e20a9693b86b3ae09e10d88c Author: mountain <mountain@action2quare.com> Date: Wed Jul 19 09:33:37 2023 +0900 타입 이름 변경 commit 256bfd030c294d2d7bea4ca2c3082f2d49ae9aef Author: mountain <mountain@action2quare.com> Date: Wed Jul 19 09:31:01 2023 +0900 redison 추가 commit 72a683fed2c024616b2171be1f6841a11150a3a8 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 19:51:24 2023 +0900 gob에 []any 추가 commit 89fa9e4ac585026c331d697929697af5defc6b9d Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:45:12 2023 +0900 write control 수정 commit d724cc84fa94ab6cdd3d8bddd3ab08c99d0aef3a Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:38:04 2023 +0900 redis pubsub 채널 이름에 디비 인덱스 추가 commit 8df248fa54908a8cb547813179e4269e25c7df87 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:20:47 2023 +0900 close를 writecontrol로 변경 commit 40a603522d4082f426a0d3818d852db53e458cd2 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 12:21:06 2023 +0900 conn에 msg를 쓰는 쓰레드 단일화 commit c21017d2cd8b2bd26bdbf6d4eca49d06b8462ce0 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 11:08:38 2023 +0900 redis call이 문제가 아니었음 commit 82abcddb497bdb95b0eff2d7a98b72cacf37bc9a Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 11:04:15 2023 +0900 잦은 redis call 회피 commit 289af24a8ffaa55336bfabca151a71f6e1f82290 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 09:55:18 2023 +0900 room create 메시지 전송 commit 4b35e0e6386b1a27e4dec854d1fc2c755f20aeef Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 09:45:27 2023 +0900 EventReceiver 인터페이스 추가 commit 29843802ff0eb6378af63b2a14de826a594f5a9e Author: mountain <mountain@action2quare.com> Date: Mon Jul 17 17:45:40 2023 +0900 gob 등록 commit 66aea48fb7322728d0f665e37b2dd818f441c26f Author: mountain <mountain@action2quare.com> Date: Sun Jul 16 18:39:11 2023 +0900 채널간 publish marshalling을 gob으로 변경 commit 8f6c87a8aeb86f8fb41ba7a5ff75e3e8ee9ede2c Author: mountain <mountain@action2quare.com> Date: Sun Jul 16 16:37:02 2023 +0900 redis option을 copy로 변경 commit f0f459332d1a62a938a9b9b7ca34e3d3a2f26e8c Author: mountain <mountain@action2quare.com> Date: Sat Jul 15 17:08:33 2023 +0900 wshandler에서 authcache제거하고 config 포맷 변경
2023-07-19 09:35:25 +09:00
sessionSyncChannelNamePrefix = "session-sync-channel2"
2023-05-24 15:10:15 +09:00
)
type AuthinfoCell interface {
ToBytes() []byte
ToAuthinfo() *Authinfo
}
type AuthCollection struct {
lock sync.Mutex
// key : session
auths map[string]*Authinfo
expiring map[string]*Authinfo
// accid -> session (auths)
reverseOn map[primitive.ObjectID]string
// accid -> session (expiring)
reverseOff map[primitive.ObjectID]string
nextTrim time.Time
ttl time.Duration
SessionRemoved func(string)
SessionAdded func(AuthinfoCell)
QuerySession func(string, string) AuthinfoCell
Stop func()
}
func MakeAuthCollection(sessionTTL time.Duration) *AuthCollection {
return &AuthCollection{
auths: make(map[string]*Authinfo),
expiring: make(map[string]*Authinfo),
reverseOn: make(map[primitive.ObjectID]string),
reverseOff: make(map[primitive.ObjectID]string),
nextTrim: time.Now().Add(time.Hour * 1000000),
ttl: sessionTTL,
SessionRemoved: func(string) {},
SessionAdded: func(AuthinfoCell) {},
QuerySession: func(string, string) AuthinfoCell { return nil },
}
}
type authCollectionConfig struct {
RegionStorageConfig
Maingate string `json:"maingate_service_url"`
}
type redisAuthCell struct {
raw []byte
}
func (ac *redisAuthCell) ToAuthinfo() *Authinfo {
var out Authinfo
err := json.Unmarshal(ac.raw, &out)
if err != nil {
logger.Error("redisAuthCell ToAuthinfo failed :", string(ac.raw), err)
return nil
}
return &out
}
func (ac *redisAuthCell) ToBytes() []byte {
return ac.raw
}
func newAuthCollectionWithRedis(redisClient *redis.Client, subctx context.Context, maingateURL string, apiToken string) *AuthCollection {
sessionTTL := int64(3600)
ac := MakeAuthCollection(time.Duration(sessionTTL * int64(time.Second)))
Squashed commit of the following: commit 29b2f258507d9e11e20a9693b86b3ae09e10d88c Author: mountain <mountain@action2quare.com> Date: Wed Jul 19 09:33:37 2023 +0900 타입 이름 변경 commit 256bfd030c294d2d7bea4ca2c3082f2d49ae9aef Author: mountain <mountain@action2quare.com> Date: Wed Jul 19 09:31:01 2023 +0900 redison 추가 commit 72a683fed2c024616b2171be1f6841a11150a3a8 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 19:51:24 2023 +0900 gob에 []any 추가 commit 89fa9e4ac585026c331d697929697af5defc6b9d Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:45:12 2023 +0900 write control 수정 commit d724cc84fa94ab6cdd3d8bddd3ab08c99d0aef3a Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:38:04 2023 +0900 redis pubsub 채널 이름에 디비 인덱스 추가 commit 8df248fa54908a8cb547813179e4269e25c7df87 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:20:47 2023 +0900 close를 writecontrol로 변경 commit 40a603522d4082f426a0d3818d852db53e458cd2 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 12:21:06 2023 +0900 conn에 msg를 쓰는 쓰레드 단일화 commit c21017d2cd8b2bd26bdbf6d4eca49d06b8462ce0 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 11:08:38 2023 +0900 redis call이 문제가 아니었음 commit 82abcddb497bdb95b0eff2d7a98b72cacf37bc9a Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 11:04:15 2023 +0900 잦은 redis call 회피 commit 289af24a8ffaa55336bfabca151a71f6e1f82290 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 09:55:18 2023 +0900 room create 메시지 전송 commit 4b35e0e6386b1a27e4dec854d1fc2c755f20aeef Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 09:45:27 2023 +0900 EventReceiver 인터페이스 추가 commit 29843802ff0eb6378af63b2a14de826a594f5a9e Author: mountain <mountain@action2quare.com> Date: Mon Jul 17 17:45:40 2023 +0900 gob 등록 commit 66aea48fb7322728d0f665e37b2dd818f441c26f Author: mountain <mountain@action2quare.com> Date: Sun Jul 16 18:39:11 2023 +0900 채널간 publish marshalling을 gob으로 변경 commit 8f6c87a8aeb86f8fb41ba7a5ff75e3e8ee9ede2c Author: mountain <mountain@action2quare.com> Date: Sun Jul 16 16:37:02 2023 +0900 redis option을 copy로 변경 commit f0f459332d1a62a938a9b9b7ca34e3d3a2f26e8c Author: mountain <mountain@action2quare.com> Date: Sat Jul 15 17:08:33 2023 +0900 wshandler에서 authcache제거하고 config 포맷 변경
2023-07-19 09:35:25 +09:00
sessionSyncChannelName := fmt.Sprintf("%s-%d", sessionSyncChannelNamePrefix, redisClient.Options().DB)
2023-05-24 15:10:15 +09:00
pubsub := redisClient.Subscribe(subctx, sessionSyncChannelName)
ctx, cancel := context.WithCancel(context.TODO())
go func(ctx context.Context, sub *redis.PubSub, authCache *AuthCollection) {
for {
select {
case <-ctx.Done():
return
case msg := <-sub.Channel():
if msg == nil {
return
}
if len(msg.Payload) == 0 {
continue
}
if msg.Payload[0] == '-' {
authCache.RemoveBySessionKey(msg.Payload[1:], false)
} else {
authCache.AddRaw(&redisAuthCell{
raw: []byte(msg.Payload),
})
}
}
}
}(ctx, pubsub, ac)
ac.Stop = cancel
ac.QuerySession = func(key string, token string) AuthinfoCell {
req, _ := http.NewRequest("GET", fmt.Sprintf("%s/query?sk=%s", maingateURL, key), nil)
req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("MG-X-API-TOKEN", apiToken)
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
logger.Error("authorize query failed :", err)
return nil
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if len(raw) == 0 {
// 세션키가 없네? 클라이언트한테 재로그인하라고 알려줘야 함
return nil
}
return &redisAuthCell{
raw: raw,
}
}
ac.SessionAdded = func(cell AuthinfoCell) {
redisClient.Publish(context.Background(), sessionSyncChannelName, cell.ToBytes())
}
ac.SessionRemoved = func(sk string) {
redisClient.Publish(context.Background(), sessionSyncChannelName, "-"+sk)
}
return ac
}
type AuthCollectionGlobal struct {
apiToken string
ptr unsafe.Pointer // map[string]*AuthCollection
}
func (acg *AuthCollectionGlobal) Get(region string) *AuthCollection {
ptr := atomic.LoadPointer(&acg.ptr)
oldval := *(*map[string]*AuthCollection)(ptr)
return oldval[region]
}
func (acg *AuthCollectionGlobal) Regions() (out []string) {
ptr := atomic.LoadPointer(&acg.ptr)
oldval := *(*map[string]*AuthCollection)(ptr)
for k := range oldval {
out = append(out, k)
}
return
}
func (acg *AuthCollectionGlobal) Reload(context context.Context) error {
ptr := atomic.LoadPointer(&acg.ptr)
oldval := *(*map[string]*AuthCollection)(ptr)
var config authCollectionConfig
if err := LoadConfig(&config); err != nil {
return err
}
newval := make(map[string]*AuthCollection)
for r, c := range oldval {
if _, ok := config.RegionStorage[r]; !ok {
// 없어졌네? 닫음
c.Stop()
} else {
newval[r] = c
}
}
for r, url := range config.RegionStorage {
if _, ok := oldval[r]; !ok {
// 새로 생겼네
Squashed commit of the following: commit 29b2f258507d9e11e20a9693b86b3ae09e10d88c Author: mountain <mountain@action2quare.com> Date: Wed Jul 19 09:33:37 2023 +0900 타입 이름 변경 commit 256bfd030c294d2d7bea4ca2c3082f2d49ae9aef Author: mountain <mountain@action2quare.com> Date: Wed Jul 19 09:31:01 2023 +0900 redison 추가 commit 72a683fed2c024616b2171be1f6841a11150a3a8 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 19:51:24 2023 +0900 gob에 []any 추가 commit 89fa9e4ac585026c331d697929697af5defc6b9d Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:45:12 2023 +0900 write control 수정 commit d724cc84fa94ab6cdd3d8bddd3ab08c99d0aef3a Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:38:04 2023 +0900 redis pubsub 채널 이름에 디비 인덱스 추가 commit 8df248fa54908a8cb547813179e4269e25c7df87 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:20:47 2023 +0900 close를 writecontrol로 변경 commit 40a603522d4082f426a0d3818d852db53e458cd2 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 12:21:06 2023 +0900 conn에 msg를 쓰는 쓰레드 단일화 commit c21017d2cd8b2bd26bdbf6d4eca49d06b8462ce0 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 11:08:38 2023 +0900 redis call이 문제가 아니었음 commit 82abcddb497bdb95b0eff2d7a98b72cacf37bc9a Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 11:04:15 2023 +0900 잦은 redis call 회피 commit 289af24a8ffaa55336bfabca151a71f6e1f82290 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 09:55:18 2023 +0900 room create 메시지 전송 commit 4b35e0e6386b1a27e4dec854d1fc2c755f20aeef Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 09:45:27 2023 +0900 EventReceiver 인터페이스 추가 commit 29843802ff0eb6378af63b2a14de826a594f5a9e Author: mountain <mountain@action2quare.com> Date: Mon Jul 17 17:45:40 2023 +0900 gob 등록 commit 66aea48fb7322728d0f665e37b2dd818f441c26f Author: mountain <mountain@action2quare.com> Date: Sun Jul 16 18:39:11 2023 +0900 채널간 publish marshalling을 gob으로 변경 commit 8f6c87a8aeb86f8fb41ba7a5ff75e3e8ee9ede2c Author: mountain <mountain@action2quare.com> Date: Sun Jul 16 16:37:02 2023 +0900 redis option을 copy로 변경 commit f0f459332d1a62a938a9b9b7ca34e3d3a2f26e8c Author: mountain <mountain@action2quare.com> Date: Sat Jul 15 17:08:33 2023 +0900 wshandler에서 authcache제거하고 config 포맷 변경
2023-07-19 09:35:25 +09:00
redisClient, err := NewRedisClient(url.Redis["session"])
2023-05-24 15:10:15 +09:00
if err != nil {
return err
}
if authCache := newAuthCollectionWithRedis(redisClient, context, config.Maingate, acg.apiToken); authCache != nil {
newval[r] = authCache
}
}
}
atomic.StorePointer(&acg.ptr, unsafe.Pointer(&newval))
return nil
}
func NewAuthCollectionGlobal(context context.Context, apiToken string) (AuthCollectionGlobal, error) {
var config authCollectionConfig
if err := LoadConfig(&config); err != nil {
return AuthCollectionGlobal{}, err
}
output := make(map[string]*AuthCollection)
for region, url := range config.RegionStorage {
Squashed commit of the following: commit 29b2f258507d9e11e20a9693b86b3ae09e10d88c Author: mountain <mountain@action2quare.com> Date: Wed Jul 19 09:33:37 2023 +0900 타입 이름 변경 commit 256bfd030c294d2d7bea4ca2c3082f2d49ae9aef Author: mountain <mountain@action2quare.com> Date: Wed Jul 19 09:31:01 2023 +0900 redison 추가 commit 72a683fed2c024616b2171be1f6841a11150a3a8 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 19:51:24 2023 +0900 gob에 []any 추가 commit 89fa9e4ac585026c331d697929697af5defc6b9d Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:45:12 2023 +0900 write control 수정 commit d724cc84fa94ab6cdd3d8bddd3ab08c99d0aef3a Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:38:04 2023 +0900 redis pubsub 채널 이름에 디비 인덱스 추가 commit 8df248fa54908a8cb547813179e4269e25c7df87 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 17:20:47 2023 +0900 close를 writecontrol로 변경 commit 40a603522d4082f426a0d3818d852db53e458cd2 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 12:21:06 2023 +0900 conn에 msg를 쓰는 쓰레드 단일화 commit c21017d2cd8b2bd26bdbf6d4eca49d06b8462ce0 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 11:08:38 2023 +0900 redis call이 문제가 아니었음 commit 82abcddb497bdb95b0eff2d7a98b72cacf37bc9a Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 11:04:15 2023 +0900 잦은 redis call 회피 commit 289af24a8ffaa55336bfabca151a71f6e1f82290 Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 09:55:18 2023 +0900 room create 메시지 전송 commit 4b35e0e6386b1a27e4dec854d1fc2c755f20aeef Author: mountain <mountain@action2quare.com> Date: Tue Jul 18 09:45:27 2023 +0900 EventReceiver 인터페이스 추가 commit 29843802ff0eb6378af63b2a14de826a594f5a9e Author: mountain <mountain@action2quare.com> Date: Mon Jul 17 17:45:40 2023 +0900 gob 등록 commit 66aea48fb7322728d0f665e37b2dd818f441c26f Author: mountain <mountain@action2quare.com> Date: Sun Jul 16 18:39:11 2023 +0900 채널간 publish marshalling을 gob으로 변경 commit 8f6c87a8aeb86f8fb41ba7a5ff75e3e8ee9ede2c Author: mountain <mountain@action2quare.com> Date: Sun Jul 16 16:37:02 2023 +0900 redis option을 copy로 변경 commit f0f459332d1a62a938a9b9b7ca34e3d3a2f26e8c Author: mountain <mountain@action2quare.com> Date: Sat Jul 15 17:08:33 2023 +0900 wshandler에서 authcache제거하고 config 포맷 변경
2023-07-19 09:35:25 +09:00
redisClient, err := NewRedisClient(url.Redis["session"])
2023-05-24 15:10:15 +09:00
if err != nil {
return AuthCollectionGlobal{}, err
}
if authCache := newAuthCollectionWithRedis(redisClient, context, config.Maingate, apiToken); authCache != nil {
output[region] = authCache
}
}
return AuthCollectionGlobal{
apiToken: apiToken,
ptr: unsafe.Pointer(&output),
}, nil
}
func (sc *AuthCollection) AddRaw(cell AuthinfoCell) {
sc.lock.Lock()
defer sc.lock.Unlock()
if time.Now().After(sc.nextTrim) {
sc.expiring, sc.auths = sc.auths, sc.expiring
sc.reverseOff, sc.reverseOn = sc.reverseOn, sc.reverseOff
// maps 패키지는 아직 0.0.0 상태;; https://pkg.go.dev/golang.org/x/exp/maps?tab=versions
// maps.Clear(sc.auths)
sc.auths = make(map[string]*Authinfo)
sc.reverseOn = make(map[primitive.ObjectID]string)
}
newauth := cell.ToAuthinfo()
if newauth == nil {
logger.Println("AuthCollection.AddRaw failed. cell.ToAuthinfo returns nil")
return
}
sk := newauth.Sk.Hex()
if oldsk, exists := sc.reverseOn[newauth.Accid]; exists {
delete(sc.auths, oldsk)
delete(sc.reverseOn, newauth.Accid)
} else if oldsk, exists = sc.reverseOff[newauth.Accid]; exists {
delete(sc.expiring, oldsk)
delete(sc.reverseOff, newauth.Accid)
}
delete(sc.auths, sk)
delete(sc.expiring, sk)
sc.auths[sk] = newauth
sc.reverseOn[newauth.Accid] = sk
if len(sc.auths) == 1 {
sc.nextTrim = time.Now().Add(sc.ttl)
}
}
func (sc *AuthCollection) Find(sk string) *Authinfo {
sc.lock.Lock()
defer sc.lock.Unlock()
if found, ok := sc.auths[sk]; ok {
return found
}
return sc.expiring[sk]
}
func (sc *AuthCollection) RemoveByAccId(accid primitive.ObjectID) {
sc.lock.Lock()
defer sc.lock.Unlock()
var sk string
if on, ok := sc.reverseOn[accid]; ok {
sk = on
} else if off, ok := sc.reverseOff[accid]; ok {
sk = off
}
if len(sk) > 0 {
old := sc.auths[sk]
if old != nil {
accid = old.Accid
delete(sc.auths, sk)
delete(sc.reverseOn, accid)
} else if old = sc.expiring[sk]; old != nil {
accid = old.Accid
delete(sc.expiring, sk)
delete(sc.reverseOff, accid)
}
}
}
func (sc *AuthCollection) RemoveBySessionKey(sk string, publish bool) (accid primitive.ObjectID) {
sc.lock.Lock()
defer sc.lock.Unlock()
if publish {
// 나한테 있든 없든 무조건 publish해야 함
sc.SessionRemoved(sk)
}
old := sc.auths[sk]
if old != nil {
accid = old.Accid
delete(sc.auths, sk)
delete(sc.reverseOn, accid)
} else if old = sc.expiring[sk]; old != nil {
accid = old.Accid
delete(sc.expiring, sk)
delete(sc.reverseOff, accid)
} else {
accid = primitive.NilObjectID
}
return
}
func (sc *AuthCollection) IsValid(sk string, token string) (accid primitive.ObjectID, success bool) {
exists := sc.Find(sk)
if exists != nil {
now := int64(primitive.NewDateTimeFromTime(time.Now().UTC()))
//if int64(exists.Expired) > now && exists.Token == token {
if int64(exists.Expired) > now { //-- accesstoken은 사실상 쓰지 않는다.
return exists.Accid, true
}
if exists.Expired == 0 {
// 이미 maingate db까지 가서 만료된 것으로 확인된 키다.
return primitive.NilObjectID, false
}
}
cell := sc.QuerySession(sk, token)
if cell == nil {
// maingate db까지 가서 만료된 것으로 확인된 키다. Expired를 0으로 저장해 놓고 쿼리를 더 이상 보내지 않도록
sc.lock.Lock()
defer sc.lock.Unlock()
sc.auths[sk] = &Authinfo{Expired: 0}
logger.Println("session is invalid. cell is nil :", sk)
return primitive.NilObjectID, false
}
newauth := cell.ToAuthinfo()
if newauth == nil {
logger.Println("session is invalid. ToAuthinfo() returns nil :", sk)
return primitive.NilObjectID, false
}
sc.AddRaw(cell)
sc.SessionAdded(cell)
return newauth.Accid, true
}