Skip to content

Commit bed6bd2

Browse files
committed
Updated auth
1 parent 6eedfb3 commit bed6bd2

File tree

5 files changed

+92
-21
lines changed

5 files changed

+92
-21
lines changed

pkg/handler/auth/endpoints.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"net/http"
66
"regexp"
7+
"strings"
78
"time"
89

910
// Packages
@@ -75,8 +76,8 @@ func (service *auth) ListTokens(w http.ResponseWriter, r *http.Request) {
7576
// Get a token
7677
func (service *auth) GetToken(w http.ResponseWriter, r *http.Request) {
7778
urlParameters := router.Params(r.Context())
78-
token := service.jar.Get(urlParameters[0])
79-
if !token.IsValid() {
79+
token := service.jar.GetWithValue(strings.ToLower(urlParameters[0]))
80+
if token.IsZero() {
8081
httpresponse.Error(w, http.StatusNotFound)
8182
return
8283
}
@@ -92,17 +93,22 @@ func (service *auth) GetToken(w http.ResponseWriter, r *http.Request) {
9293
func (service *auth) CreateToken(w http.ResponseWriter, r *http.Request) {
9394
var req TokenCreate
9495

95-
// TODO: Parse Duration
96-
// TODO: Require unique name
97-
9896
// Get the request
9997
if err := httprequest.Read(r, &req); err != nil {
10098
httpresponse.Error(w, http.StatusBadRequest, err.Error())
10199
return
102100
}
103101

102+
// Check for a valid name
103+
req.Name = strings.TrimSpace(req.Name)
104+
if req.Name == "" {
105+
httpresponse.Error(w, http.StatusBadRequest, "missing 'name'")
106+
} else if token := service.jar.GetWithName(req.Name); token.IsValid() {
107+
httpresponse.Error(w, http.StatusConflict, "duplicate 'name'")
108+
}
109+
104110
// Create the token
105-
token := NewToken(req.Name, service.tokenBytes, req.Duration, req.Scope...)
111+
token := NewToken(req.Name, service.tokenBytes, req.Duration.Duration, req.Scope...)
106112
if !token.IsValid() {
107113
httpresponse.Error(w, http.StatusInternalServerError)
108114
return
@@ -125,8 +131,8 @@ func (service *auth) CreateToken(w http.ResponseWriter, r *http.Request) {
125131
// Update an existing token
126132
func (service *auth) UpdateToken(w http.ResponseWriter, r *http.Request) {
127133
urlParameters := router.Params(r.Context())
128-
token := service.jar.Get(urlParameters[0])
129-
if !token.IsValid() {
134+
token := service.jar.GetWithValue(strings.ToLower(urlParameters[0]))
135+
if token.IsZero() {
130136
httpresponse.Error(w, http.StatusNotFound)
131137
return
132138
}
@@ -138,6 +144,8 @@ func (service *auth) UpdateToken(w http.ResponseWriter, r *http.Request) {
138144
return
139145
}
140146
default:
147+
// TODO: PATCH
148+
// Patch can be with name, expire_time, scopes
141149
httpresponse.Error(w, http.StatusMethodNotAllowed)
142150
return
143151
}

pkg/handler/auth/interface.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ type TokenJar interface {
1111
// Return all tokens
1212
Tokens() []Token
1313

14-
// Return a token from the jar, or nil if the token is not found.
15-
// The method should update the access time of the token.
16-
Get(string) Token
14+
// Return a token from the jar by value, or an invalid token
15+
// if the token is not found. The method should update the access
16+
// time of the token.
17+
GetWithValue(string) Token
18+
19+
// Return a token from the jar by name, or nil if the token
20+
// is not found. The method should not update the access time
21+
// of the token.
22+
GetWithName(string) Token
1723

1824
// Put a token into the jar, assuming it does not yet exist.
1925
Create(Token) error

pkg/handler/auth/token.go

+41-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/rand"
66
"encoding/hex"
77
"encoding/json"
8+
"fmt"
89
"slices"
910
"strconv"
1011
"time"
@@ -22,9 +23,13 @@ type Token struct {
2223
}
2324

2425
type TokenCreate struct {
25-
Name string `json:"name,omitempty"` // Name of the token
26-
Duration time.Duration `json:"duration,omitempty"` // Duration of the token, or zero for no expiration
27-
Scope []string `json:"scopes,omitempty"` // Authentication scopes
26+
Name string `json:"name,omitempty"` // Name of the token
27+
Duration duration `json:"duration,omitempty"` // Duration of the token, or zero for no expiration
28+
Scope []string `json:"scopes,omitempty"` // Authentication scopes
29+
}
30+
31+
type duration struct {
32+
time.Duration
2833
}
2934

3035
/////////////////////////////////////////////////////////////////////
@@ -83,6 +88,14 @@ func (t Token) IsValid() bool {
8388
return false
8489
}
8590

91+
// Return true if the token is a zero token
92+
func (t Token) IsZero() bool {
93+
if t.Name == "" && t.Value == "" && t.Expire.IsZero() && t.Time.IsZero() && len(t.Scope) == 0 {
94+
return true
95+
}
96+
return false
97+
}
98+
8699
// Return true if the token has the specified scope, and is valid
87100
func (t Token) IsScope(scopes ...string) bool {
88101
if !t.IsValid() {
@@ -144,6 +157,31 @@ func (t Token) MarshalJSON() ([]byte, error) {
144157
return buf.Bytes(), nil
145158
}
146159

160+
func (d duration) MarshalJSON() ([]byte, error) {
161+
return json.Marshal(d.String())
162+
}
163+
164+
func (d *duration) UnmarshalJSON(b []byte) error {
165+
var v any
166+
if err := json.Unmarshal(b, &v); err != nil {
167+
return err
168+
}
169+
switch value := v.(type) {
170+
case float64:
171+
d.Duration = time.Duration(value) * time.Second
172+
return nil
173+
case string:
174+
var err error
175+
d.Duration, err = time.ParseDuration(value)
176+
if err != nil {
177+
return err
178+
}
179+
return nil
180+
default:
181+
return fmt.Errorf("invalid duration of type %T", v)
182+
}
183+
}
184+
147185
/////////////////////////////////////////////////////////////////////
148186
// PRIVATE METHODS
149187

pkg/handler/tokenjar/tokenjar.go

+19-2
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,10 @@ func (jar *tokenjar) Tokens() []auth.Token {
135135
return result
136136
}
137137

138-
// Return a token from the jar, or nil if the token is not found.
138+
// Return a token from the jar.
139139
// The method should update the access time of the token.
140-
func (jar *tokenjar) Get(key string) auth.Token {
140+
// If token is not found, return an empty token.
141+
func (jar *tokenjar) GetWithValue(key string) auth.Token {
141142
jar.Lock()
142143
defer jar.Unlock()
143144

@@ -153,6 +154,22 @@ func (jar *tokenjar) Get(key string) auth.Token {
153154
}
154155
}
155156

157+
// Return a token from the jar by name.
158+
// The method does not update the access time of the token.
159+
// If token is not found, return an empty token.
160+
func (jar *tokenjar) GetWithName(name string) auth.Token {
161+
jar.RLock()
162+
defer jar.RUnlock()
163+
164+
for _, token := range jar.jar {
165+
if token.Name == name {
166+
return *token
167+
}
168+
}
169+
170+
return auth.Token{}
171+
}
172+
156173
// Put a token into the jar, assuming it does not yet exist.
157174
func (jar *tokenjar) Create(token auth.Token) error {
158175
jar.Lock()

pkg/handler/tokenjar/tokenjar_test.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,20 @@ func Test_tokenjar_002(t *testing.T) {
5050
// Add a token
5151
token := auth.NewToken("test", 100, 0)
5252
assert.NoError(tokens.Create(token))
53-
t.Log(token)
5453

55-
// Get a token
56-
token2 := tokens.Get(token.Value)
57-
assert.NotNil(token2)
54+
// Get a token by value
55+
token2 := tokens.GetWithValue(token.Value)
5856
assert.True(token.Equals(token2))
5957

58+
// Get a token by name
59+
token9 := tokens.GetWithName(token.Name)
60+
assert.True(token.Equals(token9))
61+
6062
// Update a token
6163
token2.Scope = []string{ScopeRead}
6264
assert.NoError(tokens.Update(token2))
6365

64-
token3 := tokens.Get(token.Value)
66+
token3 := tokens.GetWithValue(token.Value)
6567
assert.NotNil(token3)
6668
assert.True(token3.Equals(token2))
6769

0 commit comments

Comments
 (0)