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:
ErickSkrauch 2018-02-15 14:20:17 +03:00
parent 235f65f11c
commit 778bc615aa
No known key found for this signature in database
GPG Key ID: 669339FCBB30EE0E
10 changed files with 48 additions and 293 deletions

28
Gopkg.lock generated
View File

@ -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

View File

@ -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]]

View File

@ -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"
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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())
}
} }

View File

@ -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")
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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