diff --git a/.travis.yml b/.travis.yml index fb97a99..1e56e5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.12.x + - 1.13.x env: - GO111MODULE=on diff --git a/go.mod b/go.mod index 420d58c..70766f0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/mapreal19/beemiel -go 1.12 +go 1.13 require ( github.com/astaxie/beego v1.11.1 diff --git a/tokens/README.md b/tokens/README.md new file mode 100644 index 0000000..9bd5445 --- /dev/null +++ b/tokens/README.md @@ -0,0 +1,25 @@ +## Tokens + +### Access Token + +We use PASETO under the hood: https://paseto.io/ + +Set your env values first: `PASETO_SYMMETRIC_KEY` (32 bytes key) + +You could generate those using `tokens.GenerateSymmetricKey()` function: + +```go +fmt.Println("generated symmetric key: ", tokens.GenerateSymmetricKey()) +``` + +Generate new reset token: +```go +passwords.NewAccessToken(payload string, expiration time.Time) +``` + +Get Payload from reset token: +```go +passwords.GetPayloadFromToken(token string) +``` + +See tests in order to set and get a given JSON payload. diff --git a/tokens/tokens.go b/tokens/tokens.go new file mode 100644 index 0000000..8f0427e --- /dev/null +++ b/tokens/tokens.go @@ -0,0 +1,59 @@ +package tokens + +import ( + "crypto/rand" + "encoding/hex" + "os" + "time" + + "github.com/o1egl/paseto" +) + +func NewAccessToken(payload string, expiration time.Time) string { + now := time.Now() + + jsonToken := paseto.JSONToken{ + Subject: payload, + IssuedAt: now, + NotBefore: now, + Expiration: expiration, + } + + v2 := paseto.NewV2() + token, err := v2.Encrypt(symmetricKey(), jsonToken, nil) + + if err != nil { + panic(err) + } + + return token +} + +func GetPayloadFromToken(token string) (payload string, err error) { + v2 := paseto.NewV2() + jsonToken := paseto.JSONToken{} + + v2.Decrypt(token, symmetricKey(), &jsonToken, nil) + if err != nil { + return + } + + err = jsonToken.Validate() + if err != nil { + return + } + + payload = jsonToken.Subject + return +} + +func symmetricKey() (key []byte) { + key, _ = hex.DecodeString(os.Getenv("PASETO_SYMMETRIC_KEY")) + return +} + +func GenerateSymmetricKey() string { + key := make([]byte, 32) + _, _ = rand.Read(key) + return hex.EncodeToString(key) +} diff --git a/tokens/tokens_suite_test.go b/tokens/tokens_suite_test.go new file mode 100644 index 0000000..4c8f583 --- /dev/null +++ b/tokens/tokens_suite_test.go @@ -0,0 +1,13 @@ +package tokens_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestTokens(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Tokens Suite") +} diff --git a/tokens/tokens_test.go b/tokens/tokens_test.go new file mode 100644 index 0000000..0c1ad82 --- /dev/null +++ b/tokens/tokens_test.go @@ -0,0 +1,77 @@ +package tokens_test + +import ( + "encoding/json" + "os" + "time" + + "github.com/mapreal19/beemiel/tokens" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("tokens", func() { + Describe("NewAccessToken", func() { + It("returns PASETO token", func() { + _ = os.Setenv("PASETO_SYMMETRIC_KEY", tokens.GenerateSymmetricKey()) + userID := "1" + expiration := time.Now().Add(2 * time.Hour) + + result := tokens.NewAccessToken(userID, expiration) + + Expect(result).To(HavePrefix("v2.local.")) + }) + + Context("when having a wrong PASETO key", func() { + It("panics", func() { + _ = os.Setenv("PASETO_SYMMETRIC_KEY", "wannabekey") + userID := "1" + expiration := time.Now().Add(2 * time.Hour) + + result := func() { + tokens.NewAccessToken(userID, expiration) + } + + Expect(result).Should(Panic()) + }) + }) + }) + + Describe("GetPayloadFromToken", func() { + It("returns json payload", func() { + _ = os.Setenv("PASETO_SYMMETRIC_KEY", tokens.GenerateSymmetricKey()) + payload := `{"base":"BTC","currency":"USD","amount":12334.87}` + + expiration := time.Now().Add(2 * time.Hour) + token := tokens.NewAccessToken(payload, expiration) + + result, err := tokens.GetPayloadFromToken(token) + + type money struct { + Base string `json:"base"` + Currency string `json:"currency"` + Amount float32 `json:"amount"` + } + var response money + + err = json.Unmarshal([]byte(result), &response) + + Expect(err).To(BeNil()) + Expect("BTC").To(Equal(response.Base)) + Expect("USD").To(Equal(response.Currency)) + }) + + Context("when token is expired", func() { + It("returns error", func() { + _ = os.Setenv("PASETO_SYMMETRIC_KEY", tokens.GenerateSymmetricKey()) + userID := "1" + expiration := time.Now().Add(-2 * time.Hour) + token := tokens.NewAccessToken(userID, expiration) + + _, err := tokens.GetPayloadFromToken(token) + + Expect(err.Error()).To(Equal("token has expired: token validation error")) + }) + }) + }) +})