Skip to content

Commit 44f865a

Browse files
address review comments
1 parent 76fb8ed commit 44f865a

8 files changed

Lines changed: 37 additions & 30 deletions

File tree

api/tokens_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import (
99

1010
func TestTokenGeneration(t *testing.T) {
1111
clientPub, clientPriv := generateClientToken()
12-
assert.Regexp(t, regexp.MustCompile(`^gtfy_client\.(.+)$`), clientPub)
13-
assert.Regexp(t, regexp.MustCompile(`^gtfy_client\.(.+)$`), clientPriv)
12+
assert.Regexp(t, regexp.MustCompile(`^gtfyc\.(.+)$`), clientPub)
13+
assert.Regexp(t, regexp.MustCompile(`^gtfyc\.(.+)$`), clientPriv)
1414
applicationPub, applicationPriv := generateApplicationToken()
15-
assert.Regexp(t, regexp.MustCompile(`^gtfy_app\.(.+)$`), applicationPub)
16-
assert.Regexp(t, regexp.MustCompile(`^gtfy_app\.(.+)$`), applicationPriv)
15+
assert.Regexp(t, regexp.MustCompile(`^gtfya\.(.+)$`), applicationPub)
16+
assert.Regexp(t, regexp.MustCompile(`^gtfya\.(.+)$`), applicationPriv)
1717
imageName := generateImageName()
1818
assert.Regexp(t, regexp.MustCompile(`^(.+)$`), imageName)
1919
}

auth/authentication.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ func (a *Auth) evaluate(ctx *gin.Context, funcs ...func(ctx *gin.Context) (authS
9090
for _, fn := range funcs {
9191
state, err := fn(ctx)
9292
if err != nil {
93+
if errors.Is(err, errCannotParseToken) {
94+
ctx.AbortWithError(401, err)
95+
return true
96+
}
9397
ctx.AbortWithError(500, err)
9498
return true
9599
}
@@ -155,7 +159,7 @@ func (a *Auth) handleClient(checks ...func(*model.Client) (authState, error)) fu
155159
if strings.HasPrefix(token, enhancedTokenPrefix) {
156160
complexToken, err := ParseEnhancedToken(token)
157161
if err != nil || !complexToken.ValidateTimestamp(timeNow().Unix()) {
158-
return authStateSkip, nil
162+
return authStateSkip, err
159163
}
160164
token = complexToken.PublicForm()
161165
}
@@ -197,7 +201,7 @@ func (a *Auth) handleApplication(ctx *gin.Context) (authState, error) {
197201
if strings.HasPrefix(token, enhancedTokenPrefix) {
198202
complexToken, err := ParseEnhancedToken(token)
199203
if err != nil || !complexToken.ValidateTimestamp(timeNow().Unix()) {
200-
return authStateSkip, nil
204+
return authStateSkip, err
201205
}
202206
token = complexToken.PublicForm()
203207
}

auth/token.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@ const (
2020

2121
var (
2222
errInvalidToken = errors.New("invalid token")
23+
errCannotParseToken = errors.New("cannot parse token")
2324
errNoPrivateKey = errors.New("no private key")
2425
tokenCharacters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
2526
pluginPrefix = "P"
26-
enhancedTokenPrefix = "gtfy_"
27+
enhancedTokenPrefix = "gtfy"
2728

2829
randReader = rand.Reader
2930
)
3031

3132
type EnhancedToken struct {
32-
ident string // a shared identifier, in formats like A12 for application ID 12
33-
pubOrPrivKey []byte // public key
33+
ident string
34+
pubOrPrivKey []byte
3435
timestamp int64
3536
signature []byte
3637
}
@@ -51,7 +52,7 @@ func (c *EnhancedToken) Sign(timestamp int64) (*EnhancedToken, error) {
5152
}
5253
privKey := ed25519.NewKeyFromSeed(c.pubOrPrivKey)
5354
sha512 := sha512.New()
54-
sha512.Write([]byte("iss="))
55+
sha512.Write([]byte("iat="))
5556
fmt.Fprintf(sha512, "%d", timestamp)
5657
sign, err := privKey.Sign(nil, sha512.Sum(nil), crypto.SHA512)
5758
if err != nil {
@@ -107,49 +108,49 @@ func NewEnhancedToken(ident string) *EnhancedToken {
107108
func ParseEnhancedToken(token string) (*EnhancedToken, error) {
108109
token, found := strings.CutPrefix(token, enhancedTokenPrefix)
109110
if !found {
110-
return nil, errInvalidToken
111+
return nil, fmt.Errorf("%w: token must start with %s", errCannotParseToken, enhancedTokenPrefix)
111112
}
112113

113114
// count number of dots, one dot -> ident then private key, three dots -> ident, public key, challenge then signature
114115
fields := strings.SplitN(token, ".", 4)
115116
if len(fields) != 2 && len(fields) != 4 {
116-
return nil, errInvalidToken
117+
return nil, fmt.Errorf("%w: token must have 2 or 4 fields separated by dots", errCannotParseToken)
117118
}
118119
ident := fields[0]
119120
pkOrPubkeyB64 := fields[1]
120121
pkOrPubkeyBytesLen := base64.RawURLEncoding.DecodedLen(len(pkOrPubkeyB64))
121122
pkOrPubkey, err := base64.RawURLEncoding.DecodeString(pkOrPubkeyB64)
122123
if err != nil {
123-
return nil, errInvalidToken
124+
return nil, fmt.Errorf("%w: base64 decode failed: %w", errCannotParseToken, err)
124125
}
125126
if len(fields) == 2 {
126127
if pkOrPubkeyBytesLen != ed25519.SeedSize {
127-
return nil, errInvalidToken
128+
return nil, fmt.Errorf("%w: private key must be %d bytes", errCannotParseToken, ed25519.SeedSize)
128129
}
129130
return &EnhancedToken{
130131
ident: ident,
131132
pubOrPrivKey: pkOrPubkey,
132133
}, nil
133134
}
134135
if pkOrPubkeyBytesLen != ed25519.PublicKeySize {
135-
return nil, errInvalidToken
136+
return nil, fmt.Errorf("%w: public key must be %d bytes", errCannotParseToken, ed25519.PublicKeySize)
136137
}
137138
timestampStr := fields[2]
138139
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
139140
if err != nil {
140-
return nil, errInvalidToken
141+
return nil, fmt.Errorf("%w: timestamp must be an integer: %w", errCannotParseToken, err)
141142
}
142143
signatureB64 := fields[3]
143144
signatureBytesLen := base64.RawURLEncoding.DecodedLen(len(signatureB64))
144145
if signatureBytesLen != ed25519.SignatureSize {
145-
return nil, errInvalidToken
146+
return nil, fmt.Errorf("%w: signature must be %d bytes", errCannotParseToken, ed25519.SignatureSize)
146147
}
147148
signature, err := base64.RawURLEncoding.DecodeString(signatureB64)
148149
if err != nil {
149-
return nil, errInvalidToken
150+
return nil, fmt.Errorf("%w: base64 decode failed: %w", errCannotParseToken, err)
150151
}
151152
sha512 := sha512.New()
152-
sha512.Write([]byte("iss=")) // query-like encoding to give us some semantic headroom should we need more fields in the future
153+
sha512.Write([]byte("iat=")) // query-like encoding to give us some semantic headroom should we need more fields in the future
153154
fmt.Fprintf(sha512, "%d", timestamp)
154155
if err := ed25519.VerifyWithOptions(pkOrPubkey, sha512.Sum(nil), signature, &ed25519.Options{Hash: crypto.SHA512}); err != nil {
155156
return nil, errInvalidToken
@@ -173,13 +174,13 @@ func randIntn(n int) int {
173174

174175
// GenerateApplicationToken generates an application token.
175176
func GenerateApplicationToken() (publicForm, privateForm string) {
176-
token := NewEnhancedToken("app")
177+
token := NewEnhancedToken("a")
177178
return token.PublicForm(), token.String()
178179
}
179180

180181
// GenerateClientToken generates a client token.
181182
func GenerateClientToken() (publicForm, privateForm string) {
182-
token := NewEnhancedToken("client")
183+
token := NewEnhancedToken("c")
183184
return token.PublicForm(), token.String()
184185
}
185186

auth/token_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ package auth
33
import (
44
"crypto/rand"
55
"errors"
6+
"strings"
67
"testing"
78
"testing/iotest"
89

910
"github.com/stretchr/testify/assert"
1011
)
1112

1213
func TestNewComplexToken(t *testing.T) {
13-
token := NewEnhancedToken("A12")
14+
token := NewEnhancedToken("a")
1415
canonicalizedExpected := token.PublicForm()
1516
tokenParsed, err := ParseEnhancedToken(token.String())
1617
assert.NoError(t, err)
@@ -32,11 +33,12 @@ func TestNewComplexToken(t *testing.T) {
3233
assert.NoError(t, err)
3334
canonicalizedActual = tokenParsed.PublicForm()
3435
assert.Equal(t, canonicalizedExpected, canonicalizedActual)
35-
tokenSignedStrMutated := tokenSignedStr[1:]
36-
if tokenSignedStr[0] != 'A' {
37-
tokenSignedStrMutated = "A" + tokenSignedStrMutated
36+
var tokenSignedStrMutated string
37+
lastDotIdx := strings.LastIndex(tokenSignedStr, ".")
38+
if tokenSignedStr[lastDotIdx+1] != 'A' {
39+
tokenSignedStrMutated = tokenSignedStr[:lastDotIdx+1] + "A" + tokenSignedStr[lastDotIdx+2:]
3840
} else {
39-
tokenSignedStrMutated = "B" + tokenSignedStrMutated
41+
tokenSignedStrMutated = tokenSignedStr[:lastDotIdx+1] + "B" + tokenSignedStr[lastDotIdx+2:]
4042
}
4143
_, err = ParseEnhancedToken(tokenSignedStrMutated)
4244
assert.ErrorIs(t, err, errInvalidToken)

model/application.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type Application struct {
1818
//
1919
// read only: true
2020
// example: AWH0wZ5r0Mbac.r
21-
Token string `gorm:"type:varchar(180);uniqueIndex:uix_applications_token" json:"token"`
21+
Token string `gorm:"type:varchar(180);uniqueIndex:uix_applications_token" json:"token,omitempty"`
2222
UserID uint `gorm:"index;uniqueIndex:uix_application_user_id_sort_key,priority:1" json:"-"`
2323
// The application name. This is how the application should be displayed to the user.
2424
//

model/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type Client struct {
1818
//
1919
// read only: true
2020
// example: CWH0wZ5r0Mbac.r
21-
Token string `gorm:"type:varchar(180);uniqueIndex:uix_clients_token" json:"token"`
21+
Token string `gorm:"type:varchar(180);uniqueIndex:uix_clients_token" json:"token,omitempty"`
2222
UserID uint `gorm:"index" json:"-"`
2323
// The client name. This is how the client should be displayed to the user.
2424
//

ui/src/tests/application.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const createApp =
6464
await page.waitForSelector($dialog.button('.finish'));
6565
await page.waitForSelector($dialog.p('.token'));
6666
const token = await innerText(page, $dialog.p('.token'));
67-
expect(token.startsWith('gtfy_app.')).toBeTruthy();
67+
expect(token.startsWith('gtfya.')).toBeTruthy();
6868
await page.click($dialog.button('.finish'));
6969
await waitToDisappear(page, $dialog.selector());
7070
};

ui/src/tests/client.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const fillClientDialog =
5050
if (hasToken) {
5151
await page.waitForSelector($dialog.p('.token'));
5252
const token = await innerText(page, $dialog.p('.token'));
53-
expect(token.startsWith('gtfy_client.')).toBeTruthy();
53+
expect(token.startsWith('gtfyc.')).toBeTruthy();
5454
await page.waitForSelector($dialog.button('.finish'));
5555
await page.click($dialog.button('.finish'));
5656
}

0 commit comments

Comments
 (0)