2324c5d576
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
2.1 KiB
Go
90 lines
2.1 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
func TestMintSessionToken_ReturnsUrlSafeBase64(t *testing.T) {
|
|
token, err := MintSessionToken()
|
|
if err != nil {
|
|
t.Fatalf("MintSessionToken: %v", err)
|
|
}
|
|
if len(token) < 40 {
|
|
t.Errorf("token length = %d, want >= 40 (32B base64 url-safe)", len(token))
|
|
}
|
|
if strings.ContainsAny(token, "+/=") {
|
|
t.Errorf("token %q contains non-url-safe chars", token)
|
|
}
|
|
}
|
|
|
|
func TestHashSessionToken_IsDeterministicSHA256(t *testing.T) {
|
|
token := "test-token-xyz"
|
|
want := sha256.Sum256([]byte(token))
|
|
|
|
got := HashSessionToken(token)
|
|
if len(got) != sha256.Size {
|
|
t.Fatalf("hash length = %d, want %d", len(got), sha256.Size)
|
|
}
|
|
if base64.StdEncoding.EncodeToString(got) != base64.StdEncoding.EncodeToString(want[:]) {
|
|
t.Errorf("hash = %x, want %x", got, want)
|
|
}
|
|
}
|
|
|
|
func TestVerifyPassword(t *testing.T) {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte("hunter2"), bcrypt.MinCost)
|
|
if err != nil {
|
|
t.Fatalf("GenerateFromPassword: %v", err)
|
|
}
|
|
if !VerifyPassword(string(hash), "hunter2") {
|
|
t.Error("correct password rejected")
|
|
}
|
|
if VerifyPassword(string(hash), "wrong") {
|
|
t.Error("wrong password accepted")
|
|
}
|
|
if VerifyPassword("not-a-hash", "hunter2") {
|
|
t.Error("malformed hash accepted")
|
|
}
|
|
}
|
|
|
|
func TestRequireUser_RejectsWhenNoCookieOrBearer(t *testing.T) {
|
|
next := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
|
t.Fatal("handler must not be called")
|
|
})
|
|
h := RequireUser(nil)(next)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/me", nil)
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("status = %d, want 401", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestExtractBearerToken(t *testing.T) {
|
|
cases := []struct {
|
|
header string
|
|
want string
|
|
}{
|
|
{"", ""},
|
|
{"Bearer abc", "abc"},
|
|
{"bearer abc", "abc"},
|
|
{"Token abc", ""},
|
|
{"Bearer", ""},
|
|
{"Bearer whitespace-token ", "whitespace-token"},
|
|
}
|
|
for _, c := range cases {
|
|
got := extractBearerToken(c.header)
|
|
if got != c.want {
|
|
t.Errorf("extractBearerToken(%q) = %q, want %q", c.header, got, c.want)
|
|
}
|
|
}
|
|
}
|