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 }