diff --git a/README.md b/README.md index c817e9b9..5ec5836f 100644 --- a/README.md +++ b/README.md @@ -394,6 +394,16 @@ authorization { } ``` +Or, if you chose to use a token: + +``` +authorization { + # You can generate the token using /util/mkpassword.go + token: $2a$11$pBwUBpza8vdJ7tWZcP5GRO13qRgh4dwNn8g67k5i/41yIKBp.sHke + timeout: 1 +} +``` + **Multi-user authentication** You can enable multi-user authentication using a NATS server configuration file that defines user credentials (`user` and `password`), and optionally `permissions`, for two or more users. Multi-user authentication leverages [variables](#variables). diff --git a/server/opts.go b/server/opts.go index ede0a134..c9fc3e75 100644 --- a/server/opts.go +++ b/server/opts.go @@ -88,8 +88,9 @@ type Options struct { // Configuration file authorization section. type authorization struct { // Singles - user string - pass string + user string + pass string + token string // Multiple Users users []*User timeout float64 @@ -174,12 +175,19 @@ func ProcessConfigFile(configFile string) (*Options, error) { } opts.Username = auth.user opts.Password = auth.pass + opts.Authorization = auth.token + if (auth.user != "" || auth.pass != "") && auth.token != "" { + return nil, fmt.Errorf("Cannot have a user/pass and token") + } opts.AuthTimeout = auth.timeout // Check for multiple users defined if auth.users != nil { if auth.user != "" { return nil, fmt.Errorf("Can not have a single user/pass and a users array") } + if auth.token != "" { + return nil, fmt.Errorf("Can not have a token and a users array") + } opts.Users = auth.users } case "http": @@ -340,6 +348,8 @@ func parseAuthorization(am map[string]interface{}) (*authorization, error) { auth.user = mv.(string) case "pass", "password": auth.pass = mv.(string) + case "token": + auth.token = mv.(string) case "timeout": at := float64(1) switch mv.(type) { diff --git a/server/opts_test.go b/server/opts_test.go index 1d2d6f18..bd2198b7 100644 --- a/server/opts_test.go +++ b/server/opts_test.go @@ -4,8 +4,11 @@ package server import ( "crypto/tls" + "io/ioutil" "net/url" + "os" "reflect" + "strings" "testing" "time" ) @@ -605,3 +608,46 @@ func TestAuthorizationConfig(t *testing.T) { t.Fatalf("Expected Susan's subscribe permissions to be 'PUBLIC.>', got %q\n", subPerm) } } + +func TestTokenWithUserPass(t *testing.T) { + confFileName := "test.conf" + defer os.Remove(confFileName) + content := ` + authorization={ + user: user + pass: password + token: $2a$11$whatever + }` + if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil { + t.Fatalf("Error writing config file: %v", err) + } + _, err := ProcessConfigFile(confFileName) + if err == nil { + t.Fatal("Expected error, got none") + } + if !strings.Contains(err.Error(), "token") { + t.Fatalf("Expected error related to token, got %v", err) + } +} + +func TestTokenWithUsers(t *testing.T) { + confFileName := "test.conf" + defer os.Remove(confFileName) + content := ` + authorization={ + token: $2a$11$whatever + users: [ + {user: test, password: test} + ] + }` + if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil { + t.Fatalf("Error writing config file: %v", err) + } + _, err := ProcessConfigFile(confFileName) + if err == nil { + t.Fatal("Expected error, got none") + } + if !strings.Contains(err.Error(), "token") { + t.Fatalf("Expected error related to token, got %v", err) + } +} diff --git a/test/client_auth_test.go b/test/client_auth_test.go index ea519d8d..f8dac7c0 100644 --- a/test/client_auth_test.go +++ b/test/client_auth_test.go @@ -4,6 +4,8 @@ package test import ( "fmt" + "io/ioutil" + "os" "testing" "github.com/nats-io/go-nats" @@ -48,3 +50,32 @@ func TestMultipleUserAuth(t *testing.T) { } defer nc.Close() } + +// Resolves to "test" +const testToken = "$2a$05$3sSWEVA1eMCbV0hWavDjXOx.ClBjI6u1CuUdLqf22cbJjXsnzz8/." + +func TestTokenInConfig(t *testing.T) { + confFileName := "test.conf" + defer os.Remove(confFileName) + content := ` + listen: 127.0.0.1:4567 + authorization={ + token: ` + testToken + ` + timeout: 5 + }` + if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil { + t.Fatalf("Error writing config file: %v", err) + } + s, opts := RunServerWithConfig(confFileName) + defer s.Shutdown() + + url := fmt.Sprintf("nats://test@%s:%d/", opts.Host, opts.Port) + nc, err := nats.Connect(url) + if err != nil { + t.Fatalf("Expected a successful connect, got %v\n", err) + } + defer nc.Close() + if !nc.AuthRequired() { + t.Fatal("Expected auth to be required for the server") + } +}