mirror of
https://github.com/elyby/chrly.git
synced 2024-12-23 21:50:03 +05:30
The configuration file was deleted in favor of using environment variables.
Token generation functionality remove. Secret token now provided via CHRLY_SECRET env variable.
This commit is contained in:
parent
235f65f11c
commit
778bc615aa
28
Gopkg.lock
generated
28
Gopkg.lock
generated
@ -55,12 +55,6 @@
|
|||||||
packages = [".","hcl/ast","hcl/parser","hcl/scanner","hcl/strconv","hcl/token","json/parser","json/scanner","json/token"]
|
packages = [".","hcl/ast","hcl/parser","hcl/scanner","hcl/strconv","hcl/token","json/parser","json/scanner","json/token"]
|
||||||
revision = "8f6b1344a92ff8877cf24a5de9177bf7d0a2a187"
|
revision = "8f6b1344a92ff8877cf24a5de9177bf7d0a2a187"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/howeyc/gopass"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/inconshreveable/mousetrap"
|
name = "github.com/inconshreveable/mousetrap"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
@ -79,12 +73,6 @@
|
|||||||
packages = ["cluster","pool","redis","util"]
|
packages = ["cluster","pool","redis","util"]
|
||||||
revision = "d234cfb904a91daafa4e1f92599a893b349cc0c2"
|
revision = "d234cfb904a91daafa4e1f92599a893b349cc0c2"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/mitchellh/go-homedir"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/mitchellh/mapstructure"
|
name = "github.com/mitchellh/mapstructure"
|
||||||
@ -121,12 +109,6 @@
|
|||||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/segmentio/go-prompt"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "f0d19b6901ade831d5a3204edc0d6a7d6457fbb2"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/spf13/afero"
|
name = "github.com/spf13/afero"
|
||||||
@ -176,16 +158,10 @@
|
|||||||
revision = "59055296916bb3c6ad9cf3b21d5f2cf7059f8e76"
|
revision = "59055296916bb3c6ad9cf3b21d5f2cf7059f8e76"
|
||||||
source = "https://github.com/erickskrauch/govalidator.git"
|
source = "https://github.com/erickskrauch/govalidator.git"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
packages = ["ssh/terminal"]
|
|
||||||
revision = "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = ["unix","windows"]
|
packages = ["unix"]
|
||||||
revision = "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce"
|
revision = "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
@ -203,6 +179,6 @@
|
|||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "85c318cc67a4e78dd3608297ae189cc70b07968ba6e0e1a04cc21b264fddf1eb"
|
inputs-digest = "e6bd87f630333e3e5b03bea33720c3281a9094551bd5ced436062157fe51ab71"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
11
Gopkg.toml
11
Gopkg.toml
@ -24,22 +24,11 @@ ignored = ["elyby/minecraft-skinsystem"]
|
|||||||
name = "github.com/SermoDigital/jose"
|
name = "github.com/SermoDigital/jose"
|
||||||
version = "~1.1.0"
|
version = "~1.1.0"
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/mitchellh/go-homedir"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/segmentio/go-prompt"
|
|
||||||
branch = "master"
|
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/thedevsaddam/govalidator"
|
name = "github.com/thedevsaddam/govalidator"
|
||||||
source = "https://github.com/erickskrauch/govalidator.git"
|
source = "https://github.com/erickskrauch/govalidator.git"
|
||||||
branch = "issue-18"
|
branch = "issue-18"
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/spf13/afero"
|
|
||||||
|
|
||||||
# Testing dependencies
|
# Testing dependencies
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
|
112
auth/jwt.go
112
auth/jwt.go
@ -1,25 +1,17 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"errors"
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SermoDigital/jose/crypto"
|
"github.com/SermoDigital/jose/crypto"
|
||||||
"github.com/SermoDigital/jose/jws"
|
"github.com/SermoDigital/jose/jws"
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var fs = afero.NewOsFs()
|
|
||||||
|
|
||||||
var hashAlg = crypto.SigningMethodHS256
|
var hashAlg = crypto.SigningMethodHS256
|
||||||
|
|
||||||
const appHomeDirName = ".minecraft-skinsystem"
|
|
||||||
const scopesClaim = "scopes"
|
const scopesClaim = "scopes"
|
||||||
|
|
||||||
type Scope string
|
type Scope string
|
||||||
@ -29,20 +21,19 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type JwtAuth struct {
|
type JwtAuth struct {
|
||||||
signingKey []byte
|
Key []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *JwtAuth) NewToken(scopes ...Scope) ([]byte, error) {
|
func (t *JwtAuth) NewToken(scopes ...Scope) ([]byte, error) {
|
||||||
key, err := t.getSigningKey()
|
if len(t.Key) == 0 {
|
||||||
if err != nil {
|
return nil, errors.New("signing key not available")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
claims := jws.Claims{}
|
claims := jws.Claims{}
|
||||||
claims.Set(scopesClaim, scopes)
|
claims.Set(scopesClaim, scopes)
|
||||||
claims.SetIssuedAt(time.Now())
|
claims.SetIssuedAt(time.Now())
|
||||||
encoder := jws.NewJWT(claims, hashAlg)
|
encoder := jws.NewJWT(claims, hashAlg)
|
||||||
token, err := encoder.Serialize(key)
|
token, err := encoder.Serialize(t.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -50,20 +41,11 @@ func (t *JwtAuth) NewToken(scopes ...Scope) ([]byte, error) {
|
|||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *JwtAuth) GenerateSigningKey() error {
|
|
||||||
if err := createAppHomeDir(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := generateRandomBytes(64)
|
|
||||||
if err := afero.WriteFile(fs, getKeyPath(), key, 0600); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JwtAuth) Check(req *http.Request) error {
|
func (t *JwtAuth) Check(req *http.Request) error {
|
||||||
|
if len(t.Key) == 0 {
|
||||||
|
return &Unauthorized{"Signing key not set"}
|
||||||
|
}
|
||||||
|
|
||||||
bearerToken := req.Header.Get("Authorization")
|
bearerToken := req.Header.Get("Authorization")
|
||||||
if bearerToken == "" {
|
if bearerToken == "" {
|
||||||
return &Unauthorized{"Authentication header not presented"}
|
return &Unauthorized{"Authentication header not presented"}
|
||||||
@ -79,79 +61,14 @@ func (t *JwtAuth) Check(req *http.Request) error {
|
|||||||
return &Unauthorized{"Cannot parse passed JWT token"}
|
return &Unauthorized{"Cannot parse passed JWT token"}
|
||||||
}
|
}
|
||||||
|
|
||||||
signKey, err := t.getSigningKey()
|
err = token.Validate(t.Key, hashAlg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &Unauthorized{"JWT token have invalid signature. It may be corrupted or expired."}
|
||||||
}
|
|
||||||
|
|
||||||
err = token.Validate(signKey, hashAlg)
|
|
||||||
if err != nil {
|
|
||||||
return &Unauthorized{"JWT token have invalid signature. It corrupted or expired."}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *JwtAuth) getSigningKey() ([]byte, error) {
|
|
||||||
if t.signingKey == nil {
|
|
||||||
path := getKeyPath()
|
|
||||||
if _, err := fs.Stat(path); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, &SigningKeyNotAvailable{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := afero.ReadFile(fs, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.signingKey = key
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.signingKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createAppHomeDir() error {
|
|
||||||
path := getAppHomeDirPath()
|
|
||||||
if _, err := fs.Stat(path); os.IsNotExist(err) {
|
|
||||||
err := fs.Mkdir(path, 0755) // rwx r-x r-x
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAppHomeDirPath() string {
|
|
||||||
path, err := homedir.Expand("~/" + appHomeDirName)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKeyPath() string {
|
|
||||||
return getAppHomeDirPath() + "/jwt-key"
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateRandomBytes(n int) []byte {
|
|
||||||
// base64 will increase length in 1.37 times
|
|
||||||
// +1 is needed to ensure, that after base64 we will do not have any '===' characters
|
|
||||||
randLen := int(math.Ceil(float64(n) / 1.37)) + 1
|
|
||||||
randBytes := make([]byte, randLen)
|
|
||||||
rand.Read(randBytes)
|
|
||||||
// +5 is needed to have additional buffer for the next set of XX=== characters
|
|
||||||
resBytes := make([]byte, n + 5)
|
|
||||||
base64.URLEncoding.Encode(resBytes, randBytes)
|
|
||||||
|
|
||||||
return resBytes[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
type Unauthorized struct {
|
type Unauthorized struct {
|
||||||
Reason string
|
Reason string
|
||||||
}
|
}
|
||||||
@ -163,10 +80,3 @@ func (e *Unauthorized) Error() string {
|
|||||||
|
|
||||||
return "Unauthorized"
|
return "Unauthorized"
|
||||||
}
|
}
|
||||||
|
|
||||||
type SigningKeyNotAvailable struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*SigningKeyNotAvailable) Error() string {
|
|
||||||
return "Signing key not available"
|
|
||||||
}
|
|
||||||
|
@ -2,88 +2,36 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
|
|
||||||
testify "github.com/stretchr/testify/assert"
|
testify "github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNTE2NjU4MTkzIiwic2NvcGVzIjoic2tpbiJ9.agbBS0qdyYMBaVfTZJAZcTTRgW1Y0kZty4H3N2JHBO8"
|
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNTE2NjU4MTkzIiwic2NvcGVzIjoic2tpbiJ9.agbBS0qdyYMBaVfTZJAZcTTRgW1Y0kZty4H3N2JHBO8"
|
||||||
|
|
||||||
func TestJwtAuth_NewToken_Success(t *testing.T) {
|
func TestJwtAuth_NewToken_Success(t *testing.T) {
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
|
||||||
fs.Mkdir(getAppHomeDirPath(), 0755)
|
jwt := &JwtAuth{[]byte("secret")}
|
||||||
afero.WriteFile(fs, getKeyPath(), []byte("secret"), 0600)
|
|
||||||
|
|
||||||
jwt := &JwtAuth{}
|
|
||||||
token, err := jwt.NewToken(SkinScope)
|
token, err := jwt.NewToken(SkinScope)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.NotNil(token)
|
assert.NotNil(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtAuth_NewToken_KeyNotAvailable(t *testing.T) {
|
func TestJwtAuth_NewToken_KeyNotAvailable(t *testing.T) {
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
|
||||||
fs = afero.NewMemMapFs()
|
|
||||||
|
|
||||||
jwt := &JwtAuth{}
|
jwt := &JwtAuth{}
|
||||||
token, err := jwt.NewToken(SkinScope)
|
token, err := jwt.NewToken(SkinScope)
|
||||||
assert.IsType(&SigningKeyNotAvailable{}, err)
|
assert.Error(err, "signing key not available")
|
||||||
assert.Nil(token)
|
assert.Nil(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtAuth_GenerateSigningKey_KeyNotExists(t *testing.T) {
|
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
|
||||||
|
|
||||||
jwt := &JwtAuth{}
|
|
||||||
err := jwt.GenerateSigningKey()
|
|
||||||
assert.Nil(err)
|
|
||||||
if _, err := fs.Stat(getAppHomeDirPath()); err != nil {
|
|
||||||
assert.Fail("directory not created")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fs.Stat(getKeyPath()); err != nil {
|
|
||||||
assert.Fail("signing file not created")
|
|
||||||
}
|
|
||||||
|
|
||||||
content, _ := afero.ReadFile(fs, getKeyPath())
|
|
||||||
assert.Len(content, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJwtAuth_GenerateSigningKey_KeyExists(t *testing.T) {
|
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
|
||||||
|
|
||||||
fs.Mkdir(getAppHomeDirPath(), 0755)
|
|
||||||
afero.WriteFile(fs, getKeyPath(), []byte("secret"), 0600)
|
|
||||||
|
|
||||||
jwt := &JwtAuth{}
|
|
||||||
err := jwt.GenerateSigningKey()
|
|
||||||
assert.Nil(err)
|
|
||||||
if _, err := fs.Stat(getAppHomeDirPath()); err != nil {
|
|
||||||
assert.Fail("directory not created")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fs.Stat(getKeyPath()); err != nil {
|
|
||||||
assert.Fail("signing file not created")
|
|
||||||
}
|
|
||||||
|
|
||||||
content, _ := afero.ReadFile(fs, getKeyPath())
|
|
||||||
assert.NotEqual([]byte("secret"), content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJwtAuth_Check_EmptyRequest(t *testing.T) {
|
func TestJwtAuth_Check_EmptyRequest(t *testing.T) {
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||||
jwt := &JwtAuth{}
|
jwt := &JwtAuth{[]byte("secret")}
|
||||||
|
|
||||||
err := jwt.Check(req)
|
err := jwt.Check(req)
|
||||||
assert.IsType(&Unauthorized{}, err)
|
assert.IsType(&Unauthorized{}, err)
|
||||||
@ -91,12 +39,11 @@ func TestJwtAuth_Check_EmptyRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtAuth_Check_NonBearer(t *testing.T) {
|
func TestJwtAuth_Check_NonBearer(t *testing.T) {
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||||
req.Header.Add("Authorization", "this is not jwt")
|
req.Header.Add("Authorization", "this is not jwt")
|
||||||
jwt := &JwtAuth{}
|
jwt := &JwtAuth{[]byte("secret")}
|
||||||
|
|
||||||
err := jwt.Check(req)
|
err := jwt.Check(req)
|
||||||
assert.IsType(&Unauthorized{}, err)
|
assert.IsType(&Unauthorized{}, err)
|
||||||
@ -104,12 +51,11 @@ func TestJwtAuth_Check_NonBearer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtAuth_Check_BearerButNotJwt(t *testing.T) {
|
func TestJwtAuth_Check_BearerButNotJwt(t *testing.T) {
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||||
req.Header.Add("Authorization", "Bearer thisIs.Not.Jwt")
|
req.Header.Add("Authorization", "Bearer thisIs.Not.Jwt")
|
||||||
jwt := &JwtAuth{}
|
jwt := &JwtAuth{[]byte("secret")}
|
||||||
|
|
||||||
err := jwt.Check(req)
|
err := jwt.Check(req)
|
||||||
assert.IsType(&Unauthorized{}, err)
|
assert.IsType(&Unauthorized{}, err)
|
||||||
@ -117,7 +63,6 @@ func TestJwtAuth_Check_BearerButNotJwt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtAuth_Check_SecretNotAvailable(t *testing.T) {
|
func TestJwtAuth_Check_SecretNotAvailable(t *testing.T) {
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||||
@ -125,11 +70,10 @@ func TestJwtAuth_Check_SecretNotAvailable(t *testing.T) {
|
|||||||
jwt := &JwtAuth{}
|
jwt := &JwtAuth{}
|
||||||
|
|
||||||
err := jwt.Check(req)
|
err := jwt.Check(req)
|
||||||
assert.IsType(&SigningKeyNotAvailable{}, err)
|
assert.Error(err, "Signing key not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtAuth_Check_SecretInvalid(t *testing.T) {
|
func TestJwtAuth_Check_SecretInvalid(t *testing.T) {
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||||
@ -138,11 +82,10 @@ func TestJwtAuth_Check_SecretInvalid(t *testing.T) {
|
|||||||
|
|
||||||
err := jwt.Check(req)
|
err := jwt.Check(req)
|
||||||
assert.IsType(&Unauthorized{}, err)
|
assert.IsType(&Unauthorized{}, err)
|
||||||
assert.EqualError(err, "JWT token have invalid signature. It corrupted or expired.")
|
assert.EqualError(err, "JWT token have invalid signature. It may be corrupted or expired.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtAuth_Check_Valid(t *testing.T) {
|
func TestJwtAuth_Check_Valid(t *testing.T) {
|
||||||
clearFs()
|
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||||
@ -152,17 +95,3 @@ func TestJwtAuth_Check_Valid(t *testing.T) {
|
|||||||
err := jwt.Check(req)
|
err := jwt.Check(req)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtAuth_generateRandomBytes(t *testing.T) {
|
|
||||||
assert := testify.New(t)
|
|
||||||
lengthMap := []int{12, 20, 24, 30, 32, 48, 50, 64}
|
|
||||||
for _, length := range lengthMap {
|
|
||||||
bytes := generateRandomBytes(length)
|
|
||||||
assert.Len(bytes, length)
|
|
||||||
assert.False(strings.HasSuffix(string(bytes), "="), "secret key should not ends with '=' character")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearFs() {
|
|
||||||
fs = afero.NewMemMapFs()
|
|
||||||
}
|
|
||||||
|
@ -62,12 +62,3 @@ func CreateLogger(statsdAddr string, sentryAddr string) (wd.Watchdog, error) {
|
|||||||
|
|
||||||
return wd.New("", "").WithParams(rays.Host), nil
|
return wd.New("", "").WithParams(rays.Host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RabbitMQConfig struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
Vhost string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
19
cmd/root.go
19
cmd/root.go
@ -3,6 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/bootstrap"
|
"elyby/minecraft-skinsystem/bootstrap"
|
||||||
|
|
||||||
@ -10,8 +11,6 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfgFile string
|
|
||||||
|
|
||||||
var RootCmd = &cobra.Command{
|
var RootCmd = &cobra.Command{
|
||||||
Use: "chrly",
|
Use: "chrly",
|
||||||
Short: "Implementation of Minecraft skins system server",
|
Short: "Implementation of Minecraft skins system server",
|
||||||
@ -29,22 +28,10 @@ func Execute() {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.test.yaml)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
if cfgFile != "" {
|
|
||||||
viper.SetConfigFile(cfgFile)
|
|
||||||
} else {
|
|
||||||
viper.SetConfigName("config")
|
|
||||||
viper.AddConfigPath("/etc/minecraft-skinsystem")
|
|
||||||
viper.AddConfigPath(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
|
replacer := strings.NewReplacer(".", "_")
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
viper.SetEnvKeyReplacer(replacer)
|
||||||
// TODO: show only on verbose mode
|
|
||||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ var serveCmd = &cobra.Command{
|
|||||||
SkinsRepo: skinsRepo,
|
SkinsRepo: skinsRepo,
|
||||||
CapesRepo: capesRepo,
|
CapesRepo: capesRepo,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Auth: &auth.JwtAuth{},
|
Auth: &auth.JwtAuth{Key: []byte(viper.GetString("chrly.secret"))},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cfg.Run(); err != nil {
|
if err := cfg.Run(); err != nil {
|
||||||
@ -58,4 +58,11 @@ var serveCmd = &cobra.Command{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(serveCmd)
|
RootCmd.AddCommand(serveCmd)
|
||||||
|
viper.SetDefault("server.host", "")
|
||||||
|
viper.SetDefault("server.port", 80)
|
||||||
|
viper.SetDefault("storage.redis.host", "localhost")
|
||||||
|
viper.SetDefault("storage.redis.port", 6379)
|
||||||
|
viper.SetDefault("storage.redis.poll", 10)
|
||||||
|
viper.SetDefault("storage.filesystem.basePath", "data")
|
||||||
|
viper.SetDefault("storage.filesystem.capesDirName", "capes")
|
||||||
}
|
}
|
||||||
|
48
cmd/token.go
48
cmd/token.go
@ -6,60 +6,24 @@ import (
|
|||||||
|
|
||||||
"elyby/minecraft-skinsystem/auth"
|
"elyby/minecraft-skinsystem/auth"
|
||||||
|
|
||||||
"github.com/segmentio/go-prompt"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tokenCmd = &cobra.Command{
|
var tokenCmd = &cobra.Command{
|
||||||
Use: "token",
|
Use: "token",
|
||||||
Short: "API tokens manipulation",
|
|
||||||
}
|
|
||||||
|
|
||||||
var createCmd = &cobra.Command{
|
|
||||||
Use: "create",
|
|
||||||
Short: "Creates a new token, which allows to interact with Chrly API",
|
Short: "Creates a new token, which allows to interact with Chrly API",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
jwtAuth := &auth.JwtAuth{}
|
jwtAuth := &auth.JwtAuth{Key: []byte(viper.GetString("chrly.secret"))}
|
||||||
for {
|
token, err := jwtAuth.NewToken(auth.SkinScope)
|
||||||
token, err := jwtAuth.NewToken(auth.SkinScope)
|
if err != nil {
|
||||||
if err != nil {
|
log.Fatalf("Unable to create new token. The error is %v\n", err)
|
||||||
if _, ok := err.(*auth.SigningKeyNotAvailable); !ok {
|
|
||||||
log.Fatalf("Unable to create new token. The error is %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Signing key not available. Creating...")
|
|
||||||
err := jwtAuth.GenerateSigningKey()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to generate new signing key. The error is %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%s\n", token)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var resetCmd = &cobra.Command{
|
|
||||||
Use: "reset",
|
|
||||||
Short: "Re-creates the secret key, which invalidate all tokens",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
if !prompt.Confirm("Do you really want to invalidate all exists tokens?") {
|
|
||||||
fmt.Println("Aboart.")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtAuth := &auth.JwtAuth{}
|
fmt.Printf("%s\n", token)
|
||||||
if err := jwtAuth.GenerateSigningKey(); err != nil {
|
|
||||||
log.Fatalf("Unable to generate new signing key. The error is %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Token successfully regenerated.")
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
tokenCmd.AddCommand(createCmd, resetCmd)
|
|
||||||
RootCmd.AddCommand(tokenCmd)
|
RootCmd.AddCommand(tokenCmd)
|
||||||
}
|
}
|
||||||
|
@ -236,8 +236,8 @@ func apiBadRequest(resp http.ResponseWriter, errorsPerField map[string][]string)
|
|||||||
func apiForbidden(resp http.ResponseWriter, reason string) {
|
func apiForbidden(resp http.ResponseWriter, reason string) {
|
||||||
resp.WriteHeader(http.StatusForbidden)
|
resp.WriteHeader(http.StatusForbidden)
|
||||||
resp.Header().Set("Content-Type", "application/json")
|
resp.Header().Set("Content-Type", "application/json")
|
||||||
result, _ := json.Marshal([]interface{}{
|
result, _ := json.Marshal(map[string]interface{}{
|
||||||
reason,
|
"error": reason,
|
||||||
})
|
})
|
||||||
resp.Write(result)
|
resp.Write(result)
|
||||||
}
|
}
|
||||||
|
@ -345,9 +345,9 @@ func TestConfig_PostSkin_Unauthorized(t *testing.T) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(403, resp.StatusCode)
|
assert.Equal(403, resp.StatusCode)
|
||||||
response, _ := ioutil.ReadAll(resp.Body)
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
assert.JSONEq(`[
|
assert.JSONEq(`{
|
||||||
"Cannot parse passed JWT token"
|
"error": "Cannot parse passed JWT token"
|
||||||
]`, string(response))
|
}`, string(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig_DeleteSkinByUserId_Success(t *testing.T) {
|
func TestConfig_DeleteSkinByUserId_Success(t *testing.T) {
|
||||||
@ -475,18 +475,20 @@ func TestConfig_Authenticate_SignatureKeyNotSet(t *testing.T) {
|
|||||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
mocks.Auth.EXPECT().Check(gomock.Any()).Return(&auth.SigningKeyNotAvailable{})
|
mocks.Auth.EXPECT().Check(gomock.Any()).Return(&auth.Unauthorized{"signing key not available"})
|
||||||
mocks.Log.EXPECT().Error("Unknown error on validating api request: :err", gomock.Any())
|
|
||||||
mocks.Log.EXPECT().IncCounter("authentication.challenge", int64(1))
|
mocks.Log.EXPECT().IncCounter("authentication.challenge", int64(1))
|
||||||
|
mocks.Log.EXPECT().IncCounter("authentication.failed", int64(1))
|
||||||
|
|
||||||
res := config.Authenticate(http.HandlerFunc(func (resp http.ResponseWriter, req *http.Request) {}))
|
res := config.Authenticate(http.HandlerFunc(func (resp http.ResponseWriter, req *http.Request) {}))
|
||||||
res.ServeHTTP(w, req)
|
res.ServeHTTP(w, req)
|
||||||
|
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(500, resp.StatusCode)
|
assert.Equal(403, resp.StatusCode)
|
||||||
response, _ := ioutil.ReadAll(resp.Body)
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
assert.Empty(response)
|
assert.JSONEq(`{
|
||||||
|
"error": "signing key not available"
|
||||||
|
}`, string(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
// base64 https://github.com/mathiasbynens/small/blob/0ca3c51/png-transparent.png
|
// base64 https://github.com/mathiasbynens/small/blob/0ca3c51/png-transparent.png
|
||||||
|
Loading…
Reference in New Issue
Block a user