Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ format:
go fix ./...

test:
go test -v $(GONODE_PLUGINS) ./test/api ./core ./core/config ./commands/server
go test $(GONODE_PLUGINS) ./test/api ./core ./core/config ./commands/server
go vet ./...
#cd explorer && npm test

Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ A prototype to store dynamic node inside a PostgreSQL database with the JSONb st

Documentation
-------------

- [Contributing](docs/contributing.md)
- [Node](docs/node.md)
- [Vault](docs/vault.md)

* [Node](docs/node.md)
* [Plugins](docs/plugins)
* [Vault](docs/plugins/vault.md)
* [Guard](docs/plugins/guard.md)
* [Contributing](docs/contributing.md)
3 changes: 3 additions & 0 deletions commands/server/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/rande/gonode/core/config"
"github.com/rande/gonode/plugins/api"
"github.com/rande/gonode/plugins/guard"
"github.com/rande/gonode/plugins/setup"
"github.com/zenazn/goji/bind"
"github.com/zenazn/goji/graceful"
Expand Down Expand Up @@ -54,9 +55,11 @@ func (c *ServerCommand) Run(args []string) int {
l := goapp.NewLifecycle()

ConfigureServer(l, conf)

// add plugins
setup.ConfigureServer(l, conf)
api.ConfigureServer(l, conf)
guard.ConfigureServer(l, conf)

l.Run(func(app *goapp.App, state *goapp.GoroutineState) error {
mux := app.Get("goji.mux").(*web.Mux)
Expand Down
13 changes: 11 additions & 2 deletions core/config/config_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@

package config

type ServerAuth struct {
type ServerGuard struct {
Key string `toml:"key"`
Jwt struct {
Validity int64 `toml:"validity"`
Login struct {
Path string `toml:"path"`
} `toml:"login"`
Token struct {
Path string `toml:"path"`
} `toml:"token"`
} `toml:"jwt"`
}

type ServerDatabase struct {
Expand All @@ -33,7 +42,7 @@ type ServerConfig struct {
Filesystem ServerFilesystem `toml:"filesystem"`
Test bool `toml:"test"`
Bind string `toml:"bind"`
Auth ServerAuth `toml:"auth"`
Guard ServerGuard `toml:"guard"`
}

func NewServerConfig() *ServerConfig {
Expand Down
13 changes: 13 additions & 0 deletions core/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package config

import (
"bytes"
"github.com/BurntSushi/toml"
"github.com/stretchr/testify/assert"
"os"
"testing"
Expand Down Expand Up @@ -33,4 +35,15 @@ func Test_Server_LoadConfiguration(t *testing.T) {
assert.Equal(t, config.Databases["master"].Prefix, "test")
assert.Equal(t, config.Filesystem.Type, "") // not used for now
assert.Equal(t, config.Filesystem.Path, "/tmp/gnode")

assert.Equal(t, config.Guard.Jwt.Login.Path, "/login")
assert.Equal(t, config.Guard.Jwt.Token.Path, `^\/nodes\/(.*)$`)

config.Guard.Jwt.Login.Path = `^\/nodes\/(.*)$`

w := bytes.NewBufferString("")
e := toml.NewEncoder(w)

e.Encode(config)

}
91 changes: 91 additions & 0 deletions docs/plugins/guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
Guard
=====

Introduction
------------

Guard plugin handle request authentification. The plugins comes with a dedicated middleware and request authenticators.
For now there is only 2 authenticators implemented:
- ``JwtLoginGuardAuthenticator``: create a valid Json Web Token from the posted ``username`` and ``password``.
- ``JwtTokenGuardAuthenticator``: validate a Json Web Token.

Configuration
-------------


```toml
[guard]
key = "ZeSecretKey0oo"

[guard.jwt]
[guard.jwt.login]
path = "/login"

[guard.jwt.token]
path = "^\\/nodes\\/(.*)$"


- ``key`` is private and it is used to sign the JWT with a symetric algorythm.
- ``guard.jwt.login.path`` is used to configure the login entry point, ie where the ``JwtLoginGuardAuthenticator`` will accept the request.
- ``guard.jwt.token.path`` is used to configure paths requiring to have authentification handled by the ``JwtTokenGuardAuthenticator`` service.


Authenticators
--------------

### JwtLoginGuardAuthenticator


The service will use the ``core.user`` node type to find the user by her/his username. The query looks like: ``type = 'core.user' AND data->>'username' = ?``

The authentification request should be a POST

```HTTP
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&password=secret
```

If the response is valid, the response will be:

```HTTP
HTTP/1.1 200 OK
Content-Type: application/json

{
"status": "OK",
"message": "Request is authenticated",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NTA0Nzg1NzQsInJscyI6bnVsbCwidXNyIjoicmFuZGUifQ.E_BMRg2UWO7jVw1CGgn7WhhwbATCHjYYcausZZ7LSZA",
}

```

If the response is not valid, the response will be

```HTTP
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
"status": "KO",
"message": "Unable to authenticate request"
}
```

### JwtTokenGuardAuthenticator

The service will use the ``core.user`` node type to find the user by her/his username. The query looks like: ``type = 'core.user' AND data->>'username' = ?``

The authentification request should be on any http method, either using the ``Authorization`` header or the ``access_token`` parameter.

```HTTP
GET /nodes HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NTA0Nzg1NzQsInJscyI6bnVsbCwidXNyIjoicmFuZGUifQ.E_BMRg2UWO7jVw1CGgn7WhhwbATCHjYYcausZZ7LSZA
```

or

```HTTP
GET /nodes?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NTA0Nzg1NzQsInJscyI6bnVsbCwidXNyIjoicmFuZGUifQ.E_BMRg2UWO7jVw1CGgn7WhhwbATCHjYYcausZZ7LSZA HTTP/1.1
```
File renamed without changes.
2 changes: 1 addition & 1 deletion plugins/api/api_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func ConfigureServer(l *goapp.Lifecycle, conf *config.ServerConfig) {
// Set some claims
token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
// Sign and get the complete encoded token as a string
tokenString, err := token.SignedString([]byte(conf.Auth.Key))
tokenString, err := token.SignedString([]byte(conf.Guard.Key))

if err != nil {
helper.SendWithHttpCode(res, http.StatusInternalServerError, "Unable to sign the token")
Expand Down
96 changes: 96 additions & 0 deletions plugins/guard/guard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright © 2014-2015 Thomas Rabaix <thomas.rabaix@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package guard

import (
"errors"
"net/http"
)

var (
InvalidCredentialsFormat = errors.New("Invalid credentials format")
InvalidCredentials = errors.New("Invalid credentials")
UnableRetrieveUser = errors.New("Unable to retrieve the user")
CredentialMismatch = errors.New("Credential mismatch")
AuthenticatedTokenCreationError = errors.New("Unable to create authentication token")
)

// Bare interface with the default requirement to check username and password
type GuardUser interface {
GetUsername() string
GetPassword() string
GetRoles() []string
}

type DefaultGuardUser struct {
Username string
Password string
Roles []string
}

func (u *DefaultGuardUser) GetUsername() string {
return u.Username
}

func (u *DefaultGuardUser) GetPassword() string {
return u.Password
}

func (u *DefaultGuardUser) GetRoles() []string {
return u.Roles
}

// Bare interface to used inside a request lifecycle
type GuardToken interface {
// return the current username for the current token
GetUsername() string

// return the related roles linked to the current token
GetRoles() []string
}

// Default implementation to the GuardToken
type DefaultGuardToken struct {
Username string
Roles []string
}

func (t *DefaultGuardToken) GetUsername() string {
return t.Username
}

func (t *DefaultGuardToken) GetRoles() []string {
return t.Roles
}

type GuardAuthenticator interface {
// This method is call on each request.
// If the method return nil as interface{} value, it means the authenticator
// cannot handle the request
getCredentials(req *http.Request) (interface{}, error)

// Return the user from the credentials
getUser(credentials interface{}) (GuardUser, error)

// Check if the provided credentials are valid for the current user
checkCredentials(credentials interface{}, user GuardUser) error

// Return a security token related to the user
createAuthenticatedToken(u GuardUser) (GuardToken, error)

// Action when the authentication fail.
// On a default form login, it can be used to redirect the user to login page
// return true if the workflows must be stopped (ie, the authenticator was written
// bytes on the response. false if not.
onAuthenticationFailure(req *http.Request, res http.ResponseWriter, err error) bool

// Action when the authentication success
// On a default form login, it can be used to redirect the user to protected page
// or the homepage
// return true if the workflows must be stopped (ie, the authenticator was written
// bytes on the response. false if not.
onAuthenticationSuccess(req *http.Request, res http.ResponseWriter, token GuardToken) bool
}
42 changes: 42 additions & 0 deletions plugins/guard/guard_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright © 2014-2015 Thomas Rabaix <thomas.rabaix@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package guard

import (
"github.com/rande/goapp"
"github.com/rande/gonode/core"
"github.com/rande/gonode/core/config"
"github.com/zenazn/goji/web"
"regexp"
)

func ConfigureServer(l *goapp.Lifecycle, conf *config.ServerConfig) {

l.Prepare(func(app *goapp.App) error {
mux := app.Get("goji.mux").(*web.Mux)
conf := app.Get("gonode.configuration").(*config.ServerConfig)
manager := app.Get("gonode.manager").(*core.PgNodeManager)

auths := []GuardAuthenticator{
&JwtTokenGuardAuthenticator{
Path: regexp.MustCompile(conf.Guard.Jwt.Token.Path),
Key: []byte(conf.Guard.Key),
Validity: conf.Guard.Jwt.Validity,
NodeManager: manager,
},
&JwtLoginGuardAuthenticator{
LoginPath: conf.Guard.Jwt.Login.Path,
Key: []byte(conf.Guard.Key),
Validity: conf.Guard.Jwt.Validity,
NodeManager: manager,
},
}

mux.Use(GetGuardMiddleware(auths))

return nil
})
}
Loading