feat(jwt): add jwt helpers
This commit is contained in:
parent
fbbfdf8df6
commit
a99e990270
27
data/private
Normal file
27
data/private
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAl6nmCqsNninrds4Y0MXJkonE/a8/ak+tIWn8/af0nR/r+wlx
|
||||||
|
ISfAMkoAaGuhpj39v6um+iQlnBjZoo/G8+HuanAv7wxERr9l995nUNBm+EelY0KL
|
||||||
|
mG4Xh6b4qEYY2bFbHYsKiDEvxDBiaALdRLeplELw/pyoWJp6ALusbDSDJ1AG9FZ7
|
||||||
|
JYq0xNUgcydYN9PNao1pfe4A8ZkxBktJyoLK8CtjjAAZ54WwpzQx3abmVh506fS5
|
||||||
|
3lrXYIOJaW0dxcI5tT5PrKJkkE4UP551HMW/V0GVtWfHebtCutW9Jq5/uBtQW3Kg
|
||||||
|
sdNdvm+DZVrME8TqKWMQ6utz9u8DQ+YF2/DB4wIDAQABAoIBADMFPdUy7BbcJVFH
|
||||||
|
TgPVtdTtMe7hqKS7/xMxk6FFgj3lgj5mU7+Cnt6MFI0MQEorqpRzS231AQ39MiHE
|
||||||
|
2noq9EisSwPRDZr7QnNbR0hhg3Jcr9+vgEScLKA+5IG/axa42l0a7EUavuXyHPi+
|
||||||
|
le9LFepBhs8wplWASjC68etI0yJC/i5p5W8RNiBOTMrRB73LAgLYy0TcZ6OPMAor
|
||||||
|
PXyNLdTtDe5CXc4KbL3D1u8VARJ8b8+Ck7ObBg5p9qnr+VcTZLG13Af2/aEh9zDm
|
||||||
|
prU0i5obBNYsy/OFG+IZfnK0Pm56ZpkiOFSDI6f8d00BwVF0OBC4dftAgCACi62x
|
||||||
|
QDD7evECgYEAx2kARY5Nnk/98hAygiUpI8wdOlF6rCCjPUZNfJbhwqj2rame9Q4Y
|
||||||
|
nvBhqGX1SJBD5ps5zjlSL6ilwr+j3cyiAHQl3szgvdy9C9J48RkWjME6LsS8x2Yl
|
||||||
|
aDtomzZ2a4+iOzcYzaxVX3nVn4RoS+gsMiX7jDEVocyYCc5s2Hkt9GsCgYEAwrQm
|
||||||
|
hb7rX/yEQw3Ds4TbjKxIDaqsA1yRikE8hQPZ8p8SZmZIhtWjVSDvBgyCHJO1iOim
|
||||||
|
AW/72f2+kyrUXuffyzXZW7s/3KW1AHNlOKJCraBMRVtzvUt1oHL8ZrYYXbKDW6Eq
|
||||||
|
Zbae+s5n2kU8UrtaXs1qh4EhYnTO+M5NdgxXBmkCgYAk6gcm2ST9PYmhGeZ/uSlY
|
||||||
|
exyeAx9WZeRSH4WQns3EH0sq8s9+RdHA+nbZmaZCfJJVSj71Mh9Iu0uUNa28DXmf
|
||||||
|
4+Bu0jZ4bzh/y8KfvykxfUOsDLd1oi8ikHzY3sglOT2rAJQS3uge+IrXMMet5Zjo
|
||||||
|
36clWKDMhvdOOWxk1mnvaQKBgCbAnHo6SbbNF7YQ40azxs7061JtCdeRcRZHbbg7
|
||||||
|
0AFOT+c5rG3Jz7x91ZUqoCr360XYqFHY7BOzQV8hQyuwkwZrLVvopQlRofj4/siK
|
||||||
|
4yKTqRqU3TBr+Hl66Wm4DJl5klOGfF3KP1JECr+S0DLXP2FnGTDnLrHd9ePni9tX
|
||||||
|
EWshAoGAIMMr4+eYD2MIZkST6Nwbw+0BNTfiIW18CQUUwPeEHG1+CA5KT9tL5Zvz
|
||||||
|
vJ/4aFNoFPZ04IBmNmGO5LLRGvMsIJs85niqRcGIPrQq948vQ2QCQ5/W9GwxE4lu
|
||||||
|
x+yGW+XUExRP4FtTISWDMb0ZQERLjo6oBiKJkGYowzapIHCnP5Q=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
9
data/public
Normal file
9
data/public
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl6nmCqsNninrds4Y0MXJ
|
||||||
|
konE/a8/ak+tIWn8/af0nR/r+wlxISfAMkoAaGuhpj39v6um+iQlnBjZoo/G8+Hu
|
||||||
|
anAv7wxERr9l995nUNBm+EelY0KLmG4Xh6b4qEYY2bFbHYsKiDEvxDBiaALdRLep
|
||||||
|
lELw/pyoWJp6ALusbDSDJ1AG9FZ7JYq0xNUgcydYN9PNao1pfe4A8ZkxBktJyoLK
|
||||||
|
8CtjjAAZ54WwpzQx3abmVh506fS53lrXYIOJaW0dxcI5tT5PrKJkkE4UP551HMW/
|
||||||
|
V0GVtWfHebtCutW9Jq5/uBtQW3KgsdNdvm+DZVrME8TqKWMQ6utz9u8DQ+YF2/DB
|
||||||
|
4wIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module git.rinsvent.ru/rinsvent/go-jwt
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
|
github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
17
go.sum
Normal file
17
go.sum
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90 h1:rB0J+hLNltG1Qv+UF+MkdFz89XMps5BOAFJN4xWjc+s=
|
||||||
|
github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
200
jwt.go
Normal file
200
jwt.go
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RefreshTokenClaims struct {
|
||||||
|
JWT
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateRefreshTokenByAccess(accessClaims JWT, ttl time.Duration) RefreshTokenClaims {
|
||||||
|
return RefreshTokenClaims{
|
||||||
|
JWT: JWT{
|
||||||
|
Type: "refresh",
|
||||||
|
Ttl: ttl,
|
||||||
|
SessionId: accessClaims.SessionId,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ID: accessClaims.ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseRefreshToken(token string, publicKey *rsa.PublicKey) (*RefreshTokenClaims, error) {
|
||||||
|
refreshDecodedClaims, err := Decode(token, &RefreshTokenClaims{}, publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
refreshTokenClaims, ok := refreshDecodedClaims.(*RefreshTokenClaims)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid refresh token claims")
|
||||||
|
}
|
||||||
|
return refreshTokenClaims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWT struct {
|
||||||
|
Type string `json:"t"`
|
||||||
|
Ttl time.Duration `json:"td"`
|
||||||
|
SessionId string `json:"si,omitempty"`
|
||||||
|
AuthorizationInfo string `json:"ai,omitempty"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) WithTtl(ttl time.Duration) *JWT {
|
||||||
|
j.Ttl = ttl
|
||||||
|
j.ExpiresAt = jwt.NewNumericDate(time.Now().Add(ttl))
|
||||||
|
j.IssuedAt = jwt.NewNumericDate(time.Now())
|
||||||
|
j.NotBefore = jwt.NewNumericDate(time.Now())
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) WithId(id string) *JWT {
|
||||||
|
j.ID = id
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) WithSessionId(sessionId string) *JWT {
|
||||||
|
j.SessionId = sessionId
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) WithAuthorizationInfo(tai TokenAuthorizationInfo, secret string) *JWT {
|
||||||
|
ciphertext, err := tai.WithSecret(secret).Encode()
|
||||||
|
if err != nil {
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
j.AuthorizationInfo = ciphertext
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) GetAuthorizationInfo(secret string) *TokenAuthorizationInfo {
|
||||||
|
tai, err := DecodeTokenAuthorizationInfo(j.AuthorizationInfo, secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return tai
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) IsRefreshToken() bool {
|
||||||
|
return j.Type == "refresh"
|
||||||
|
}
|
||||||
|
|
||||||
|
func Encode(j interface{}, privateKey *rsa.PrivateKey) (string, error) {
|
||||||
|
payload, ok := j.(jwt.Claims)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("invalid jwt claims")
|
||||||
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, payload)
|
||||||
|
ss, err := token.SignedString(privateKey)
|
||||||
|
return ss, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(token string, data jwt.Claims, publicKey *rsa.PublicKey, options ...jwt.ParserOption) (jwt.Claims, error) {
|
||||||
|
t, err := jwt.ParseWithClaims(token, data, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return publicKey, nil
|
||||||
|
}, options...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := t.Claims.(jwt.Claims); ok {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unknown claims type, cannot proceed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadPublicKey(path string) (*rsa.PublicKey, error) {
|
||||||
|
b, err := readPublicKey(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted, err := decodePEMBlock(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding PEM block failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedKey, err := x509.ParsePKIXPublicKey(decrypted)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing decrypted public key failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, ok := parsedKey.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("parsing decrypted public key failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(path string) ([]byte, error) {
|
||||||
|
file, err := os.Open(filepath.Clean(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("opening file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPublicKey(path string) ([]byte, error) {
|
||||||
|
b, err := readFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadPrivateKey(path string) (*rsa.PrivateKey, error) {
|
||||||
|
b, err := readFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted, err := decodePEMBlock(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode PEM block: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedKey, err := x509.ParsePKCS1PrivateKey(decrypted)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing decrypted private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodePEMBlock(block []byte) ([]byte, error) {
|
||||||
|
decodedKey, _ := pem.Decode(block)
|
||||||
|
if decodedKey == nil {
|
||||||
|
return nil, fmt.Errorf("decoding PEM block failed")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
decrypted = decodedKey.Bytes
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
//nolint:staticcheck,nolintlint
|
||||||
|
if x509.IsEncryptedPEMBlock(decodedKey) {
|
||||||
|
//nolint:staticcheck,nolintlint
|
||||||
|
if decrypted, err = x509.DecryptPEMBlock(decodedKey, []byte("")); err != nil {
|
||||||
|
return nil, fmt.Errorf("decrypting PEM key failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decrypted, nil
|
||||||
|
}
|
40
jwt_test.go
Normal file
40
jwt_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccessTokenClaims struct {
|
||||||
|
UserId string `json:"id"`
|
||||||
|
FirstName string `json:"fn"`
|
||||||
|
LastName string `json:"ln"`
|
||||||
|
JWT
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJWT(t *testing.T) {
|
||||||
|
dir := "./data/"
|
||||||
|
|
||||||
|
accessClaims := AccessTokenClaims{
|
||||||
|
UserId: "123",
|
||||||
|
FirstName: "Igor",
|
||||||
|
LastName: "Sypachev",
|
||||||
|
}
|
||||||
|
accessClaims.
|
||||||
|
WithId("sadfswdf").
|
||||||
|
WithTtl(20 * time.Minute).
|
||||||
|
WithSessionId("wergwergw")
|
||||||
|
|
||||||
|
privateKey, _ := ReadPrivateKey(filepath.Join(filepath.Dir(dir), "private"))
|
||||||
|
token, err := Encode(accessClaims, privateKey)
|
||||||
|
assert.Equal(t, true, err == nil)
|
||||||
|
|
||||||
|
publicKey, _ := ReadPublicKey(filepath.Join(filepath.Dir(dir), "public"))
|
||||||
|
decodedClaims, _ := Decode(token, &AccessTokenClaims{}, publicKey)
|
||||||
|
f, _ := decodedClaims.(*AccessTokenClaims)
|
||||||
|
assert.Equal(t, f.UserId, "123")
|
||||||
|
assert.Equal(t, f.FirstName, "Igor")
|
||||||
|
assert.Equal(t, f.LastName, "Sypachev")
|
||||||
|
}
|
140
token_authorization_info.go
Normal file
140
token_authorization_info.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
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
|
||||||
|
}
|
42
token_authorization_info_test.go
Normal file
42
token_authorization_info_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTAI(t *testing.T) {
|
||||||
|
ip := net.ParseIP("192.186.4.33")
|
||||||
|
userAgent := "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||||
|
deviceId := "6a22eeeb-966c-47fa-bff8-f83dc7929d84"
|
||||||
|
cookieData := "asdfasd"
|
||||||
|
|
||||||
|
tai := TokenAuthorizationInfo{
|
||||||
|
Ip: ip,
|
||||||
|
UserAgent: userAgent,
|
||||||
|
DeviceId: deviceId,
|
||||||
|
CookieData: cookieData,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := tai.WithSecret("1234567890123456789012345678901234567890").Encode()
|
||||||
|
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
"BbFhu340tDQl9y8siWFlc7s1TpjaHpWWG9tlOGXGOLheBj+cOiF4HKUaBFou10WX8y/feoz6tz/9IPgiUTwbEuXetGIO1KdoygmYiRhxlBYqv0sRa55EjNnPS1DrM7KHOu4fyV57+dvfc4dR669lnuTwhQFE6Q51pq5FtLTnm02HisPGVl1G3JukKAjPRNWCwdZhOylGPuQCav1Egihcz2ZZ3RRDOwUu3SsKEZJJig56XAd1J5MMHzovEgg6B4J4",
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
|
||||||
|
tai2, err2 := DecodeTokenAuthorizationInfo(
|
||||||
|
"BbFhu340tDQl9y8siWFlc7s1TpjaHpWWG9tlOGXGOLheBj+cOiF4HKUaBFou10WX8y/feoz6tz/9IPgiUTwbEuXetGIO1KdoygmYiRhxlBYqv0sRa55EjNnPS1DrM7KHOu4fyV57+dvfc4dR669lnuTwhQFE6Q51pq5FtLTnm02HisPGVl1G3JukKAjPRNWCwdZhOylGPuQCav1Egihcz2ZZ3RRDOwUu3SsKEZJJig56XAd1J5MMHzovEgg6B4J4",
|
||||||
|
"1234567890123456789012345678901234567890",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, nil, err2)
|
||||||
|
assert.Equal(t, ip, tai2.Ip)
|
||||||
|
assert.Equal(t, userAgent, tai2.UserAgent)
|
||||||
|
assert.Equal(t, deviceId, tai2.DeviceId)
|
||||||
|
assert.Equal(t, cookieData, tai2.CookieData)
|
||||||
|
|
||||||
|
assert.Equal(t, true, tai.Equal(*tai2))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user