go-jwt/token_authorization_info.go

141 lines
3.6 KiB
Go
Raw Permalink Normal View History

2025-02-14 19:46:57 +03:00
package jwt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/ua-parser/uap-go/uaparser"
"net"
)
type TokenAuthorizationInfo struct {
Ip net.IP `json:"ip,omitempty"`
UserAgent string `json:"ua,omitempty"`
DeviceId string `json:"di,omitempty"`
CookieData string `json:"cd,omitempty"`
secret string
}
func (tai *TokenAuthorizationInfo) WithSecret(secret string) *TokenAuthorizationInfo {
tai.secret = secret
return tai
}
func (tai *TokenAuthorizationInfo) GetUserAgentInfo() *uaparser.Client {
parser := uaparser.NewFromSaved()
return parser.Parse(tai.UserAgent)
}
func (tai *TokenAuthorizationInfo) Encode() (string, error) {
payload, err := json.Marshal(tai)
if err != nil {
return "", err
}
ciphertext, err := GetAESEncrypted(string(payload), tai.secret[0:32], tai.secret[0:16])
if err != nil {
return "", err
}
return ciphertext, nil
}
func DecodeTokenAuthorizationInfo(ciphertext string, secret string) (*TokenAuthorizationInfo, error) {
payload, err := GetAESDecrypted(ciphertext, secret[0:32], secret[0:16])
if err != nil {
return nil, err
}
tai := TokenAuthorizationInfo{}
if err := json.Unmarshal(payload, &tai); err != nil {
return nil, err
}
return &tai, nil
}
func (tai *TokenAuthorizationInfo) Equal(requestAuthorizationInfo TokenAuthorizationInfo) bool {
// todo проработать логику проверки данных клиента. Возможно добавим finger print / http only cookie. Защита от потери токенов
if tai.Ip.String() == requestAuthorizationInfo.Ip.String() {
return true
}
uaClient := tai.GetUserAgentInfo()
requestUaClient := tai.GetUserAgentInfo()
if uaClient.UserAgent.Family == requestUaClient.UserAgent.Family &&
uaClient.UserAgent.Major == requestUaClient.UserAgent.Major &&
uaClient.Os.Family == requestUaClient.Os.Family &&
uaClient.Os.Major == requestUaClient.Os.Major &&
uaClient.Device.Family == requestUaClient.Device.Family &&
uaClient.Device.Model == requestUaClient.Device.Model &&
uaClient.Device.Brand == requestUaClient.Device.Brand {
return true
}
return false
}
func GetAESDecrypted(encrypted string, key string, iv string) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return nil, err
}
block, err := aes.NewCipher([]byte(key))
if err != nil {
return nil, err
}
if len(ciphertext)%aes.BlockSize != 0 {
return nil, fmt.Errorf("block size cant be zero")
}
mode := cipher.NewCBCDecrypter(block, []byte(iv))
mode.CryptBlocks(ciphertext, ciphertext)
ciphertext = PKCS5UnPadding(ciphertext)
return ciphertext, nil
}
// PKCS5UnPadding pads a certain blob of data with necessary data to be used in AES block cipher
func PKCS5UnPadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}
// GetAESEncrypted encrypts given text in AES 256 CBC
func GetAESEncrypted(plaintext string, key string, iv string) (string, error) {
var plainTextBlock []byte
length := len(plaintext)
if length%16 != 0 {
extendBlock := 16 - (length % 16)
plainTextBlock = make([]byte, length+extendBlock)
copy(plainTextBlock[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock))
} else {
plainTextBlock = make([]byte, length)
}
copy(plainTextBlock, plaintext)
block, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}
ciphertext := make([]byte, len(plainTextBlock))
mode := cipher.NewCBCEncrypter(block, []byte(iv))
mode.CryptBlocks(ciphertext, plainTextBlock)
str := base64.StdEncoding.EncodeToString(ciphertext)
return str, nil
}