Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding pr template file #9

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bea301d
add token checking expiration checking
cikupin Nov 2, 2019
0f40201
add RenewToken()
cikupin Nov 2, 2019
fa7d02d
update readme
cikupin Nov 5, 2019
eefd209
Merge branch 'feature/renew-jwt-if-expired' into redis-storage
cikupin Nov 6, 2019
7460f98
add redis storage
cikupin Nov 8, 2019
803fa1e
improve redis storage & add getJWTExpiration()
cikupin Nov 8, 2019
860bebd
add token expiration checking
cikupin Nov 11, 2019
cffba98
update go module
cikupin Nov 11, 2019
903d637
Merge pull request #1 from cikupin/redis-storage
cikupin Nov 14, 2019
29cef05
remove verify secret
cikupin Nov 29, 2019
f29f63e
Merge pull request #2 from cikupin/remove-verify-secret
cikupin Nov 29, 2019
25afcaa
fix jwt expired
cikupin Nov 30, 2019
6d77535
Merge pull request #3 from cikupin/fix-jwt-expired
cikupin Nov 30, 2019
c22546c
add send HSM method
cikupin Dec 16, 2019
eac239f
add pre create user & link user to channel method
cikupin Dec 16, 2019
80aeaaf
add basic auth support
cikupin Dec 17, 2019
7021226
add basic auth support
cikupin Dec 17, 2019
5368fd4
add response data for http status & response flag
cikupin Dec 17, 2019
1854333
update unit test
cikupin Dec 17, 2019
2fa10fc
Merge pull request #4 from cikupin/feature/add-user
cikupin Dec 17, 2019
9344dc9
export struct
cikupin Dec 17, 2019
5f77f64
Merge pull request #5 from cikupin/feature/add-user
cikupin Dec 17, 2019
4ad827f
add mapstructure
cikupin Dec 17, 2019
f3e235b
Merge pull request #6 from cikupin/feature/add-user
cikupin Dec 17, 2019
8b7ce6c
allow blank surname
cikupin Jan 6, 2020
29daa7a
Merge pull request #7 from cikupin/allow-blank-surname
cikupin Jan 6, 2020
927a61b
Updating Pull Request Template 2020-02-17
ardityawahyu Feb 17, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,9 @@ _**Note** : This a modified version version of [EddyTravels/smooch](https://gith

## Additional Feature

- Redis support as a centralized storage to store JWT token for supporting autoscaling environment.
- Token expiration & its checking.
- Renew token functionality whenever token is expired.
- Redis support as a centralized storage to store JWT token for supporting autoscaling environment. Use redigo as redis library.

## Tips

@@ -33,6 +35,7 @@ func main() {
KeyID: os.Getenv("SMOOCH_KEY_ID"),
Secret: os.Getenv("SMOOCH_SECRET"),
VerifySecret: os.Getenv("SMOOCH_VERIFY_SECRET"),
RedisPool: redisPool,
})
if err != nil {
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module github.com/EddyTravels/smooch
module github.com/kitabisa/smooch

require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gomodule/redigo v2.0.0+incompatible
github.com/stretchr/testify v1.3.0
)

go 1.13
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/kitabisa/smooch v0.1.0 h1:dS+ouObVdoNFVZWMIqULMr/VbQoYhsBuSUdmp0VjbcM=
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
41 changes: 41 additions & 0 deletions jwt.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package smooch

import (
"time"

jwt "github.com/dgrijalva/jwt-go"
)

// JWTExpiration defines how many seconds jwt token is valid
const JWTExpiration = 3600

func GenerateJWT(scope string, keyID string, secret string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"scope": scope,
"exp": JWTExpiration,
})
token.Header = map[string]interface{}{
"alg": "HS256",
@@ -16,3 +22,38 @@ func GenerateJWT(scope string, keyID string, secret string) (string, error) {

return token.SignedString([]byte(secret))
}

// getJWTExpiration will get jwt expiration time
func getJWTExpiration(jwtToken string, secret string) (int64, error) {
claims := jwt.MapClaims{}

_, err := jwt.ParseWithClaims(jwtToken, &claims, func(t *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil {
return -1, err
}

expiredIn := claims["exp"].(int64) - time.Now().Unix()
return expiredIn, nil
}

// isJWTExpired will check whether Smooch JWT is expired or not.
func isJWTExpired(jwtToken string, secret string) (bool, error) {
_, err := jwt.ParseWithClaims(jwtToken, jwt.MapClaims{}, func(t *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})

if err == nil {
return false, nil
}

switch err.(type) {
case *jwt.ValidationError:
vErr := err.(*jwt.ValidationError)
if vErr.Errors == jwt.ValidationErrorExpired {
return true, nil
}
}
return false, err
}
101 changes: 91 additions & 10 deletions smooch.go
Original file line number Diff line number Diff line change
@@ -13,14 +13,22 @@ import (
"os"
"path"
"strings"
"sync"

"github.com/gomodule/redigo/redis"
"github.com/kitabisa/smooch/storage"
)

var (
ErrUserIDEmpty = errors.New("user id is empty")
ErrKeyIDEmpty = errors.New("key id is empty")
ErrSecretEmpty = errors.New("secret is empty")
ErrRedisNil = errors.New("redis pool is nil")
ErrMessageNil = errors.New("message is nil")
ErrMessageRoleEmpty = errors.New("message.Role is empty")
ErrMessageTypeEmpty = errors.New("message.Type is empty")
ErrVerifySecretEmpty = errors.New("verify secret is empty")
ErrDecodeToken = errors.New("error decode token")
)

const (
@@ -46,12 +54,15 @@ type Options struct {
Logger Logger
Region string
HttpClient *http.Client
RedisPool *redis.Pool
}

type WebhookEventHandler func(payload *Payload)

type Client interface {
Handler() http.Handler
IsJWTExpired() (bool, error)
RenewToken() (string, error)
AddWebhookEventHandler(handler WebhookEventHandler)
Send(userID string, message *Message) (*ResponsePayload, error)
VerifyRequest(r *http.Request) bool
@@ -63,19 +74,34 @@ type Client interface {
type smoochClient struct {
mux *http.ServeMux
appID string
jwtToken string
keyID string
secret string
verifySecret string
logger Logger
region string
webhookEventHandlers []WebhookEventHandler
httpClient *http.Client
mtx sync.Mutex
RedisStorage *storage.RedisStorage
}

func New(o Options) (*smoochClient, error) {
if o.KeyID == "" {
return nil, ErrKeyIDEmpty
}

if o.Secret == "" {
return nil, ErrSecretEmpty
}

if o.VerifySecret == "" {
return nil, ErrVerifySecretEmpty
}

if o.RedisPool == nil {
return nil, ErrRedisNil
}

if o.Mux == nil {
o.Mux = http.NewServeMux()
}
@@ -101,19 +127,24 @@ func New(o Options) (*smoochClient, error) {
region = RegionEU
}

jwtToken, err := GenerateJWT("app", o.KeyID, o.Secret)
if err != nil {
return nil, err
}

sc := &smoochClient{
mux: o.Mux,
appID: o.AppID,
keyID: o.KeyID,
secret: o.Secret,
verifySecret: o.VerifySecret,
logger: o.Logger,
region: region,
httpClient: o.HttpClient,
jwtToken: jwtToken,
RedisStorage: storage.NewRedisStorage(o.RedisPool),
}

_, err := sc.RedisStorage.GetTokenFromRedis()
if err != nil {
_, err := sc.RenewToken()
if err != nil {
return nil, err
}
}

sc.mux.HandleFunc(o.WebhookURL, sc.handle)
@@ -124,6 +155,36 @@ func (sc *smoochClient) Handler() http.Handler {
return sc.mux
}

// IsJWTExpired will check whether Smooch JWT is expired or not.
func (sc *smoochClient) IsJWTExpired() (bool, error) {
jwtToken, err := sc.RedisStorage.GetTokenFromRedis()
if err != nil {
if err == redis.ErrNil {
return true, nil
}
return false, err
}
return isJWTExpired(jwtToken, sc.secret)
}

// RenewToken will generate new Smooch JWT token.
func (sc *smoochClient) RenewToken() (string, error) {
sc.mtx.Lock()
defer sc.mtx.Unlock()

jwtToken, err := GenerateJWT("app", sc.keyID, sc.secret)
if err != nil {
return "", err
}

err = sc.RedisStorage.SaveTokenToRedis(jwtToken, JWTExpiration)
if err != nil {
return "", err
}

return jwtToken, nil
}

func (sc *smoochClient) AddWebhookEventHandler(handler WebhookEventHandler) {
sc.webhookEventHandlers = append(sc.webhookEventHandlers, handler)
}
@@ -326,17 +387,37 @@ func (sc *smoochClient) createRequest(
buf *bytes.Buffer,
header http.Header) (*http.Request, error) {

var req *http.Request
var err error
var jwtToken string

if header == nil {
header = http.Header{}
}

if header.Get(contentTypeHeaderKey) == "" {
header.Set(contentTypeHeaderKey, contentTypeJSON)
}
header.Set(authorizationHeaderKey, fmt.Sprintf("Bearer %s", sc.jwtToken))

var req *http.Request
var err error
isExpired, err := sc.IsJWTExpired()
if err != nil {
return nil, err
}

if isExpired {
jwtToken, err = sc.RenewToken()
if err != nil {
return nil, err
}
} else {
jwtToken, err = sc.RedisStorage.GetTokenFromRedis()
if err != nil {
return nil, err
}
}

header.Set(authorizationHeaderKey, fmt.Sprintf("Bearer %s", jwtToken))

if buf == nil {
req, err = http.NewRequest(method, url, nil)
} else {
40 changes: 40 additions & 0 deletions storage/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package storage

import (
"github.com/gomodule/redigo/redis"
)

// RedisStorage defines struct property for redis storage
type RedisStorage struct {
pool *redis.Pool
jwtKey string
}

// NewRedisStorage initializes new instance of redis storage
func NewRedisStorage(p *redis.Pool) *RedisStorage {
return &RedisStorage{
pool: p,
jwtKey: "smooch-jwt-token",
}
}

// SaveTokenToRedis will save jwt token to redis
func (rs *RedisStorage) SaveTokenToRedis(token string, ttl int64) error {
conn := rs.pool.Get()
defer conn.Close()

_, err := conn.Do("SETEX", rs.jwtKey, ttl, token)
return err
}

// GetTokenFromRedis will retrieve jwt token from redis
func (rs *RedisStorage) GetTokenFromRedis() (string, error) {
conn := rs.pool.Get()
defer conn.Close()

val, err := redis.String(conn.Do("GET", rs.jwtKey))
if err != nil {
return "", err
}
return val, nil
}