Skip to content

Commit b9ffaad

Browse files
committed
Initial commit of tokenauth.
1 parent 25edf4d commit b9ffaad

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed

users/tokenauth/module.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package tokenauth
2+
3+
import (
4+
"net/http"
5+
6+
"strings"
7+
8+
"github.com/octavore/naga/service"
9+
"github.com/octavore/nagax/users"
10+
)
11+
12+
const (
13+
defaultHTTPHeader = "Authorization"
14+
defaultTokenPrefix = "token"
15+
)
16+
17+
var (
18+
_ service.Module = &Module{}
19+
_ users.Authenticator = &Module{}
20+
)
21+
22+
type TokenSource interface {
23+
Get(token string) *string
24+
}
25+
26+
type Module struct {
27+
tokenSource TokenSource
28+
header string
29+
prefix string
30+
}
31+
32+
func (m *Module) Init(c *service.Config) {
33+
c.Start = func() {
34+
m.header = defaultHTTPHeader
35+
m.prefix = defaultTokenPrefix
36+
}
37+
}
38+
39+
func (m *Module) Authenticate(rw http.ResponseWriter, req *http.Request) (bool, *string, error) {
40+
val := req.Header.Get(m.header)
41+
if val == "" {
42+
return false, nil, nil
43+
}
44+
parts := strings.SplitN(val, " ", 2)
45+
if len(parts) != 2 {
46+
return false, nil, nil
47+
}
48+
prefix := strings.ToLower(parts[0])
49+
if prefix != m.prefix {
50+
return false, nil, nil
51+
}
52+
token := parts[1]
53+
userID := m.tokenSource.Get(token)
54+
if userID == nil {
55+
return false, nil, users.ErrNotAuthorized
56+
}
57+
return true, userID, nil
58+
}
59+
60+
func (m *Module) Logout(rw http.ResponseWriter, req *http.Request) {
61+
// noop
62+
}

users/tokenauth/module_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package tokenauth
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/octavore/nagax/users"
9+
)
10+
11+
type dummyTokenSource map[string]string
12+
13+
func (d *dummyTokenSource) Get(k string) *string {
14+
v, ok := (*d)[k]
15+
if !ok {
16+
return nil
17+
}
18+
return &v
19+
}
20+
21+
func setup() (*Module, *httptest.ResponseRecorder, *http.Request) {
22+
m := &Module{
23+
tokenSource: &dummyTokenSource{"goodToken": "1234"},
24+
header: defaultHTTPHeader,
25+
prefix: defaultTokenPrefix,
26+
}
27+
return m, httptest.NewRecorder(), httptest.NewRequest("GET", "/", nil)
28+
}
29+
30+
func TestAuthenticateGoodToken(t *testing.T) {
31+
m, rw, req := setup()
32+
req.Header.Set(defaultHTTPHeader, "Token goodToken")
33+
b, s, err := m.Authenticate(rw, req)
34+
if err != nil {
35+
t.Fatal("unexpected error", err)
36+
}
37+
if !b {
38+
t.Error("unexpected value", b)
39+
}
40+
if s == nil {
41+
t.Error("unexpected value", s)
42+
} else if *s != "1234" {
43+
t.Error("unexpected value", *s)
44+
}
45+
46+
// different capitalization for prefix
47+
m, rw, req = setup()
48+
req.Header.Set(defaultHTTPHeader, "tOkEn goodToken")
49+
b, s, err = m.Authenticate(rw, req)
50+
if err != nil {
51+
t.Fatal("unexpected error", err)
52+
}
53+
if !b {
54+
t.Error("unexpected value", b)
55+
}
56+
if s == nil {
57+
t.Error("unexpected value", s)
58+
} else if *s != "1234" {
59+
t.Error("unexpected value", *s)
60+
}
61+
}
62+
63+
func TestAuthenticateBadPrefix(t *testing.T) {
64+
m, rw, req := setup()
65+
req.Header.Set(defaultHTTPHeader, "Basic badToken")
66+
67+
// returns false (not authenticated) but without an error
68+
b, s, err := m.Authenticate(rw, req)
69+
if err != nil {
70+
t.Fatal("unexpected error", nil)
71+
}
72+
if b {
73+
t.Error("unexpected value", b)
74+
}
75+
if s != nil {
76+
t.Error("unexpected value", *s)
77+
}
78+
}
79+
80+
func TestAuthenticateBadToken(t *testing.T) {
81+
m, rw, req := setup()
82+
req.Header.Set(defaultHTTPHeader, "Token badToken")
83+
// returns false (not authenticated) with an error
84+
b, s, err := m.Authenticate(rw, req)
85+
if err != users.ErrNotAuthorized {
86+
t.Fatal("unexpected error", err)
87+
}
88+
if b {
89+
t.Error("unexpected value", b)
90+
}
91+
if s != nil {
92+
t.Error("unexpected value", *s)
93+
}
94+
}

users/tokenauth/options.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package tokenauth
2+
3+
import (
4+
"strings"
5+
)
6+
7+
type option func(*Module)
8+
9+
func (m *Module) Configure(opts ...option) {
10+
for _, opt := range opts {
11+
opt(m)
12+
}
13+
}
14+
15+
// WithTokenSource sets the backing token source. Required.
16+
func WithTokenSource(ts TokenSource) option {
17+
return func(m *Module) {
18+
m.tokenSource = ts
19+
}
20+
}
21+
22+
// WithHeader sets the http header to use. Defaults to 'Authorization'. Optional.
23+
func WithHeader(header string) option {
24+
return func(m *Module) {
25+
m.header = header
26+
}
27+
}
28+
29+
// WithPrefix sets the prefix to check for in the header. Defaults to 'Token'. Optional.
30+
func WithPrefix(prefix string) option {
31+
return func(m *Module) {
32+
m.prefix = strings.ToLower(prefix)
33+
}
34+
}

0 commit comments

Comments
 (0)