mirror of https://github.com/synctv-org/synctv
Feat: crypto vendor info in db
parent
ae1f6c1407
commit
37094dd2bc
@ -1,28 +1,77 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type StreamingVendor string
|
||||
type BilibiliVendor struct {
|
||||
UserID string `gorm:"primaryKey"`
|
||||
Cookies map[string]string `gorm:"serializer:fastjson"`
|
||||
}
|
||||
|
||||
const (
|
||||
StreamingVendorBilibili StreamingVendor = "bilibili"
|
||||
StreamingVendorAlist StreamingVendor = "alist"
|
||||
)
|
||||
func (b *BilibiliVendor) BeforeSave(tx *gorm.DB) error {
|
||||
key := []byte(b.UserID)
|
||||
for k, v := range b.Cookies {
|
||||
value, err := utils.CryptoToBase64([]byte(v), key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Cookies[k] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVendor) AfterFind(tx *gorm.DB) error {
|
||||
key := []byte(b.UserID)
|
||||
for k, v := range b.Cookies {
|
||||
value, err := utils.DecryptoFromBase64(v, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Cookies[k] = string(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AlistVendor struct {
|
||||
UserID string `gorm:"primaryKey"`
|
||||
Host string `gorm:"serializer:fastjson"`
|
||||
Username string `gorm:"serializer:fastjson"`
|
||||
Password string `gorm:"serializer:fastjson"`
|
||||
}
|
||||
|
||||
type StreamingVendorInfo struct {
|
||||
UserID string `gorm:"not null;primarykey"`
|
||||
Vendor StreamingVendor `gorm:"not null;primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
VendorToken
|
||||
Host string
|
||||
func (a *AlistVendor) BeforeSave(tx *gorm.DB) error {
|
||||
key := []byte(a.UserID)
|
||||
var err error
|
||||
if a.Host, err = utils.CryptoToBase64([]byte(a.Host), key); err != nil {
|
||||
return err
|
||||
}
|
||||
if a.Username, err = utils.CryptoToBase64([]byte(a.Username), key); err != nil {
|
||||
return err
|
||||
}
|
||||
if a.Password, err = utils.CryptoToBase64([]byte(a.Password), key); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type VendorToken struct {
|
||||
Cookies []*http.Cookie `gorm:"serializer:fastjson"`
|
||||
Authorization string
|
||||
Password string
|
||||
func (a *AlistVendor) AfterFind(tx *gorm.DB) error {
|
||||
key := []byte(a.UserID)
|
||||
if v, err := utils.DecryptoFromBase64(a.Host, key); err != nil {
|
||||
return err
|
||||
} else {
|
||||
a.Host = string(v)
|
||||
}
|
||||
if v, err := utils.DecryptoFromBase64(a.Username, key); err != nil {
|
||||
return err
|
||||
} else {
|
||||
a.Username = string(v)
|
||||
}
|
||||
if v, err := utils.DecryptoFromBase64(a.Password, key); err != nil {
|
||||
return err
|
||||
} else {
|
||||
a.Password = string(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
package op
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/zijiren233/gencontainer/refreshcache"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
lock sync.RWMutex
|
||||
cache map[string]*refreshcache.RefreshCache[any]
|
||||
}
|
||||
|
||||
func newBaseCache() *Cache {
|
||||
return &Cache{
|
||||
cache: make(map[string]*refreshcache.RefreshCache[any]),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Cache) Clear() {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
b.clear()
|
||||
}
|
||||
|
||||
func (b *Cache) clear() {
|
||||
maps.Clear(b.cache)
|
||||
}
|
||||
|
||||
func (b *Cache) LoadOrStore(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
|
||||
b.lock.RLock()
|
||||
c, loaded := b.cache[id]
|
||||
if loaded {
|
||||
b.lock.RUnlock()
|
||||
return c.Get()
|
||||
}
|
||||
b.lock.RUnlock()
|
||||
b.lock.Lock()
|
||||
c, loaded = b.cache[id]
|
||||
if loaded {
|
||||
b.lock.Unlock()
|
||||
return c.Get()
|
||||
}
|
||||
c = refreshcache.NewRefreshCache[any](refreshFunc, maxAge)
|
||||
b.cache[id] = c
|
||||
b.lock.Unlock()
|
||||
return c.Get()
|
||||
}
|
||||
|
||||
func (b *Cache) StoreOrRefresh(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
|
||||
b.lock.RLock()
|
||||
c, ok := b.cache[id]
|
||||
if ok {
|
||||
b.lock.RUnlock()
|
||||
return c.Refresh()
|
||||
}
|
||||
b.lock.RUnlock()
|
||||
b.lock.Lock()
|
||||
c, ok = b.cache[id]
|
||||
if ok {
|
||||
b.lock.Unlock()
|
||||
return c.Refresh()
|
||||
}
|
||||
c = refreshcache.NewRefreshCache[any](refreshFunc, maxAge)
|
||||
b.cache[id] = c
|
||||
b.lock.Unlock()
|
||||
return c.Refresh()
|
||||
}
|
||||
|
||||
func (b *Cache) LoadOrStoreWithDynamicFunc(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
|
||||
b.lock.RLock()
|
||||
c, loaded := b.cache[id]
|
||||
if loaded {
|
||||
b.lock.RUnlock()
|
||||
return c.Data().Get(refreshFunc)
|
||||
}
|
||||
b.lock.RUnlock()
|
||||
b.lock.Lock()
|
||||
c, loaded = b.cache[id]
|
||||
if loaded {
|
||||
b.lock.Unlock()
|
||||
return c.Data().Get(refreshFunc)
|
||||
}
|
||||
c = refreshcache.NewRefreshCache[any](refreshFunc, maxAge)
|
||||
b.cache[id] = c
|
||||
b.lock.Unlock()
|
||||
return c.Data().Get(refreshFunc)
|
||||
}
|
||||
|
||||
func (b *Cache) StoreOrRefreshWithDynamicFunc(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
|
||||
b.lock.RLock()
|
||||
c, ok := b.cache[id]
|
||||
if ok {
|
||||
b.lock.RUnlock()
|
||||
return c.Data().Refresh(refreshFunc)
|
||||
}
|
||||
b.lock.RUnlock()
|
||||
b.lock.Lock()
|
||||
c, ok = b.cache[id]
|
||||
if ok {
|
||||
b.lock.Unlock()
|
||||
return c.Data().Refresh(refreshFunc)
|
||||
}
|
||||
c = refreshcache.NewRefreshCache[any](refreshFunc, maxAge)
|
||||
b.cache[id] = c
|
||||
b.lock.Unlock()
|
||||
return c.Data().Refresh(refreshFunc)
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
func Crypto(v []byte, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, aes.BlockSize+len(v))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(ciphertext[aes.BlockSize:], v)
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func Decrypto(v []byte, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(v) < aes.BlockSize {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
iv := v[:aes.BlockSize]
|
||||
v = v[aes.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(v, v)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func CryptoToBase64(v []byte, key []byte) (string, error) {
|
||||
ciphertext, err := Crypto(v, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
func DecryptoFromBase64(v string, key []byte) ([]byte, error) {
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Decrypto(ciphertext, key)
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
)
|
||||
|
||||
func TestCrypto(t *testing.T) {
|
||||
m := []byte("hello world")
|
||||
key := []byte(utils.RandString(32))
|
||||
m, err := utils.Crypto(m, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(m))
|
||||
m, err = utils.Decrypto(m, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(m))
|
||||
}
|
Loading…
Reference in New Issue