Docker & Deployment: - Add Dockerfile (multi-stage, alpine, non-root) - Add docker-compose.yml (caddy, backend, postgres, watchtower) - Add Caddyfile (TLS, file_server, reverse proxy) - Add .env.example Database: - Add migrations/001_init.sql (all tables + indexes) CI/CD: - Add cmd/ci-release/main.go (launcher binary upload tool) Session management: - Add internal/session/cleanup.go (background expired session cleanup) - Integrate cleanup worker into main.go Bug fixes: - Fix launcherLatest download URL to include version segment - Fix serveLauncherAsset path to match route pattern - Add Content-Type detection from file extension in CAS serveFile - Add empty-field validation in webLogin - Format string fix in ci-release (%d → %s for resp.Status) Tests: - Add internal/auth/auth_test.go (8 tests) - Add internal/cas/cas_test.go (7 tests) - Add internal/session/cleanup_test.go (1 test) - Add internal/api/api_test.go (5 tests) - All tests passing, go vet clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
2.8 KiB
Go
118 lines
2.8 KiB
Go
package auth
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestGenerateToken(t *testing.T) {
|
|
tok := GenerateToken()
|
|
if len(tok) != 32 {
|
|
t.Errorf("expected 32-char token, got %d chars: %s", len(tok), tok)
|
|
}
|
|
// Must be hex.
|
|
for _, c := range tok {
|
|
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
|
|
t.Errorf("token contains non-hex char: %c", c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateToken_Uniqueness(t *testing.T) {
|
|
// Two tokens should never collide.
|
|
t1 := GenerateToken()
|
|
t2 := GenerateToken()
|
|
if t1 == t2 {
|
|
t.Error("two generated tokens are identical")
|
|
}
|
|
}
|
|
|
|
func TestGenerateUUID(t *testing.T) {
|
|
uuid := GenerateUUID()
|
|
// Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars).
|
|
if len(uuid) != 36 {
|
|
t.Errorf("expected 36-char UUID, got %d: %s", len(uuid), uuid)
|
|
}
|
|
// Check dashes at correct positions.
|
|
for _, pos := range []int{8, 13, 18, 23} {
|
|
if uuid[pos] != '-' {
|
|
t.Errorf("expected dash at position %d, got %c", pos, uuid[pos])
|
|
}
|
|
}
|
|
// Version 4: char at position 14 should be '4'.
|
|
if uuid[14] != '4' {
|
|
t.Errorf("expected version 4 at position 14, got %c", uuid[14])
|
|
}
|
|
}
|
|
|
|
func TestGenerateUUID_Uniqueness(t *testing.T) {
|
|
u1 := GenerateUUID()
|
|
u2 := GenerateUUID()
|
|
if u1 == u2 {
|
|
t.Error("two generated UUIDs are identical")
|
|
}
|
|
}
|
|
|
|
func TestHashPassword(t *testing.T) {
|
|
hash, err := HashPassword("testpassword")
|
|
if err != nil {
|
|
t.Fatalf("HashPassword failed: %v", err)
|
|
}
|
|
if !strings.HasPrefix(hash, "$2a$") {
|
|
t.Errorf("expected bcrypt hash starting with $2a$, got: %s", hash[:4])
|
|
}
|
|
}
|
|
|
|
func TestVerifyPassword(t *testing.T) {
|
|
hash, err := HashPassword("minecraft123")
|
|
if err != nil {
|
|
t.Fatalf("HashPassword failed: %v", err)
|
|
}
|
|
|
|
if !VerifyPassword("minecraft123", hash) {
|
|
t.Error("VerifyPassword returned false for correct password")
|
|
}
|
|
if VerifyPassword("wrongpassword", hash) {
|
|
t.Error("VerifyPassword returned true for wrong password")
|
|
}
|
|
}
|
|
|
|
func TestIsBcryptHash(t *testing.T) {
|
|
tests := []struct {
|
|
hash string
|
|
want bool
|
|
}{
|
|
{"$2a$10$abcdefghijklmnopqrstuuxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", true},
|
|
{"$2b$10$abcdefghijklmnopqrstuuxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", true},
|
|
{"$2y$10$abcdefghijklmnopqrstuuxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", true},
|
|
{"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", false},
|
|
{"", false},
|
|
{"plaintext", false},
|
|
}
|
|
for _, tt := range tests {
|
|
got := IsBcryptHash(tt.hash)
|
|
if got != tt.want {
|
|
t.Errorf("IsBcryptHash(%q) = %v, want %v", tt.hash, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractBearer(t *testing.T) {
|
|
tests := []struct {
|
|
header string
|
|
want string
|
|
}{
|
|
{"Bearer abc123", "abc123"},
|
|
{"Bearer ", ""},
|
|
{"abc123", ""},
|
|
{"", ""},
|
|
{"Basic abc123", ""},
|
|
}
|
|
for _, tt := range tests {
|
|
got := ExtractBearer(tt.header)
|
|
if got != tt.want {
|
|
t.Errorf("ExtractBearer(%q) = %q, want %q", tt.header, got, tt.want)
|
|
}
|
|
}
|
|
}
|