Skip to content

Commit 6b5bb41

Browse files
authored
Add jwtauth middleware (#662)
* add middleware for jwt auth * fill in jwt auth functions * refactor and add tests * use viper to get jwt auth key env * remove iron token * remove unused import * Add auth tests for invalid tokens * refactor auth tests to own file * add tags to full stack tests * initial integration test * complete test tags * make make file accept tags * refactor fn to make easier testing * update integration tests * add make target for testing individual tags * update integration tests * add integration tests to circle ci * fix integration tests * add auth integration tests * set default values at env so that they are always initialized * add tests for create / delete route * add jwt route test and route call test * run server tests during test phase * add checks to confirm route being created and deleted * update documentation * cleanup test files after test
1 parent c3d25a9 commit 6b5bb41

File tree

19 files changed

+530
-174
lines changed

19 files changed

+530
-174
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ test:
1111
go test -v $(shell go list ./... | grep -v vendor | grep -v examples | grep -v tool | grep -v fn)
1212
cd fn && $(MAKE) test
1313

14+
test-tag:
15+
go test -v $(shell go list ./... | grep -v vendor | grep -v examples | grep -v tool | grep -v fn) -tags=$(TAG)
16+
1417
test-datastore:
1518
cd api/datastore && go test -v ./...
1619

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,15 @@ curl -H "Content-Type: application/json" -X POST -d '{
213213
}' http://localhost:8080/v1/apps/myapp/routes
214214
```
215215

216-
You can use JWT for [authentication](examples/jwt).
216+
[More on routes](docs/operating/routes.md).
217217

218-
[More on routes](docs/routes.md).
218+
### Authentication
219+
220+
Iron Functions API supports two levels of Authentication in two seperate scopes, service level authentication,
221+
(Which authenticates all requests made to the server from any client) and route level authentication.
222+
Route level authentication is applied whenever a function call made to a specific route.
223+
224+
Please check [Authentication](docs/authentication.md) documentation for more information.
219225

220226
### Calling your Function
221227

api/datastore/bolt/bolt_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
const tmpBolt = "/tmp/func_test_bolt.db"
1212

1313
func TestDatastore(t *testing.T) {
14-
os.Remove(tmpBolt)
1514
u, err := url.Parse("bolt://" + tmpBolt)
1615
if err != nil {
1716
t.Fatalf("failed to parse url:", err)
@@ -21,4 +20,5 @@ func TestDatastore(t *testing.T) {
2120
t.Fatalf("failed to create bolt datastore:", err)
2221
}
2322
datastoretest.Test(t, ds)
23+
os.Remove(tmpBolt)
2424
}

api/server/apps_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build server
2+
13
package server
24

35
import (

api/server/integration_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// +build integration
2+
3+
package server
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"os"
9+
"testing"
10+
"time"
11+
12+
"github.com/iron-io/functions/api/models"
13+
"github.com/iron-io/functions/fn/app"
14+
"github.com/spf13/viper"
15+
"github.com/urfave/cli"
16+
)
17+
18+
var DB_FILE string
19+
var MQ_FILE string
20+
var API_URL string
21+
var PORT int
22+
var funcServer *Server
23+
var Cancel context.CancelFunc
24+
var Ctx context.Context
25+
var fn *cli.App
26+
27+
func setupServer() {
28+
viper.Set(EnvDBURL, fmt.Sprintf("bolt://%s?bucket=funcs", DB_FILE))
29+
viper.Set(EnvMQURL, fmt.Sprintf("bolt://%s", MQ_FILE))
30+
viper.Set(EnvPort, PORT)
31+
Ctx, Cancel = context.WithCancel(context.Background())
32+
funcServer = NewFromEnv(Ctx)
33+
go funcServer.Start(Ctx)
34+
time.Sleep(2 * time.Second)
35+
}
36+
37+
func setupCli() {
38+
viper.Set("API_URL", API_URL)
39+
fn = app.NewFn()
40+
}
41+
42+
func teardown() {
43+
os.Remove(DB_FILE)
44+
os.Remove(MQ_FILE)
45+
Cancel()
46+
time.Sleep(2 * time.Second)
47+
}
48+
49+
func TestIntegration(t *testing.T) {
50+
DB_FILE = "/tmp/bolt.db"
51+
MQ_FILE = "/tmp/bolt_mq.db"
52+
PORT = 8080
53+
API_URL = "http://localhost:8080"
54+
setupServer()
55+
setupCli()
56+
testIntegration(t)
57+
teardown()
58+
}
59+
60+
func TestIntegrationWithAuth(t *testing.T) {
61+
viper.Set("jwt_auth_key", "test")
62+
DB_FILE = "/tmp/bolt_auth.db"
63+
MQ_FILE = "/tmp/bolt_auth_mq.db"
64+
PORT = 8081
65+
API_URL = "http://localhost:8081"
66+
setupServer()
67+
setupCli()
68+
testIntegration(t)
69+
teardown()
70+
}
71+
72+
func testIntegration(t *testing.T) {
73+
// Test list
74+
75+
err := fn.Run([]string{"fn", "apps", "l"})
76+
if err != nil {
77+
t.Error(err)
78+
}
79+
80+
// Test create app
81+
82+
err = fn.Run([]string{"fn", "apps", "c", "test"})
83+
if err != nil {
84+
t.Error(err)
85+
}
86+
87+
filter := &models.AppFilter{}
88+
apps, err := funcServer.Datastore.GetApps(Ctx, filter)
89+
90+
if len(apps) != 1 {
91+
t.Error("fn apps create failed.")
92+
}
93+
94+
if apps[0].Name != "test" {
95+
t.Error("fn apps create failed. - name doesnt match")
96+
}
97+
98+
// Test create route
99+
100+
err = fn.Run([]string{"fn", "routes", "c", "test", "/new-route", "--jwt-key", "route_key"})
101+
if err != nil {
102+
t.Error(err)
103+
}
104+
105+
routeFilter := &models.RouteFilter{}
106+
routes, err := funcServer.Datastore.GetRoutes(Ctx, routeFilter)
107+
108+
if len(routes) != 1 {
109+
t.Error("fn routes create failed.")
110+
}
111+
112+
if routes[0].Path != "/new-route" {
113+
t.Error("fn routes create failed. - path doesnt match")
114+
}
115+
116+
// Test call route
117+
118+
err = fn.Run([]string{"fn", "routes", "call", "test", "/new-route"})
119+
if err != nil {
120+
t.Error(err)
121+
}
122+
123+
// Test delete route
124+
125+
err = fn.Run([]string{"fn", "routes", "delete", "test", "/new-route"})
126+
if err != nil {
127+
t.Error(err)
128+
}
129+
130+
routes, err = funcServer.Datastore.GetRoutes(Ctx, routeFilter)
131+
132+
if len(routes) != 0 {
133+
t.Error("fn routes delete failed.")
134+
}
135+
136+
// Test delete app
137+
138+
err = fn.Run([]string{"fn", "apps", "delete", "test"})
139+
if err != nil {
140+
t.Error(err)
141+
}
142+
143+
apps, err = funcServer.Datastore.GetApps(Ctx, filter)
144+
145+
if len(apps) != 0 {
146+
t.Error("fn apps delete failed.")
147+
}
148+
}

api/server/middlewares.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package server
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
10+
"github.com/iron-io/functions/api/models"
11+
"github.com/iron-io/functions/common"
12+
"github.com/spf13/viper"
13+
)
14+
15+
func SetupJwtAuth(funcServer *Server) {
16+
// Add default JWT AUTH if env variable set
17+
if jwtAuthKey := viper.GetString("jwt_auth_key"); jwtAuthKey != "" {
18+
funcServer.AddMiddlewareFunc(func(ctx MiddlewareContext, w http.ResponseWriter, r *http.Request, app *models.App) error {
19+
start := time.Now()
20+
fmt.Println("JwtAuthMiddlewareFunc called at:", start)
21+
ctx.Next()
22+
fmt.Println("Duration:", (time.Now().Sub(start)))
23+
return nil
24+
})
25+
funcServer.AddMiddleware(&JwtAuthMiddleware{})
26+
}
27+
}
28+
29+
type JwtAuthMiddleware struct {
30+
}
31+
32+
func (h *JwtAuthMiddleware) Serve(ctx MiddlewareContext, w http.ResponseWriter, r *http.Request, app *models.App) error {
33+
fmt.Println("JwtAuthMiddleware called")
34+
jwtAuthKey := viper.GetString("jwt_auth_key")
35+
36+
if err := common.AuthJwt(jwtAuthKey, r); err != nil {
37+
w.WriteHeader(http.StatusUnauthorized)
38+
m := map[string]string{"error": "Invalid API Authorization token."}
39+
json.NewEncoder(w).Encode(m)
40+
return errors.New("Invalid API authorization token.")
41+
}
42+
43+
fmt.Println("auth succeeded!")
44+
return nil
45+
}

api/server/routes_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build server
2+
13
package server
24

35
import (

api/server/runner_async_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build server
2+
13
package server
24

35
import (

api/server/runner_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build server
2+
13
package server
24

35
import (

api/server/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, apiUR
9797

9898
s.Router.Use(prepareMiddleware(ctx))
9999
s.bindHandlers(ctx)
100+
s.setupMiddlewares()
100101

101102
for _, opt := range opts {
102103
opt(s)
@@ -203,6 +204,10 @@ func (s *Server) Start(ctx context.Context) {
203204
close(s.tasks)
204205
}
205206

207+
func (s *Server) setupMiddlewares() {
208+
SetupJwtAuth(s)
209+
}
210+
206211
func (s *Server) startGears(ctx context.Context) {
207212
// By default it serves on :8080 unless a
208213
// PORT environment variable was defined.

0 commit comments

Comments
 (0)