Add bcrypt user file auth, JWT middleware on all write routes, Pinia authStore with localStorage persistence, login view with redirect support, and v-if guards on all CRUD controls and admin nav link. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
126 lines
3.0 KiB
Go
126 lines
3.0 KiB
Go
package auth_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"osdb/internal/auth"
|
|
)
|
|
|
|
// hashPepper mirrors the pepper convention: bcrypt("username:password")
|
|
func hashPepper(username, password string) string {
|
|
h, err := bcrypt.GenerateFromPassword([]byte(username+":"+password), bcrypt.MinCost)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return string(h)
|
|
}
|
|
|
|
func writeTempUsersFile(t *testing.T, content string) string {
|
|
t.Helper()
|
|
f, err := os.CreateTemp(t.TempDir(), "users*.env")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := f.WriteString(content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f.Close()
|
|
return f.Name()
|
|
}
|
|
|
|
func TestLoadUsers_ParsesValidFile(t *testing.T) {
|
|
hash := hashPepper("admin", "secret")
|
|
path := writeTempUsersFile(t, "admin:"+hash+"\n")
|
|
|
|
users, err := auth.LoadUsers(path)
|
|
if err != nil {
|
|
t.Fatalf("LoadUsers: %v", err)
|
|
}
|
|
if len(users) != 1 {
|
|
t.Fatalf("expected 1 user, got %d", len(users))
|
|
}
|
|
if _, ok := users["admin"]; !ok {
|
|
t.Error("expected 'admin' in users map")
|
|
}
|
|
}
|
|
|
|
func TestLoadUsers_SkipsCommentLines(t *testing.T) {
|
|
hash := hashPepper("admin", "secret")
|
|
content := "# comment\nadmin:" + hash + "\n"
|
|
path := writeTempUsersFile(t, content)
|
|
|
|
users, err := auth.LoadUsers(path)
|
|
if err != nil {
|
|
t.Fatalf("LoadUsers: %v", err)
|
|
}
|
|
if len(users) != 1 {
|
|
t.Fatalf("expected 1 user, got %d", len(users))
|
|
}
|
|
}
|
|
|
|
func TestLoadUsers_MissingFile(t *testing.T) {
|
|
_, err := auth.LoadUsers(filepath.Join(t.TempDir(), "nonexistent.env"))
|
|
if err == nil {
|
|
t.Error("expected error for missing file")
|
|
}
|
|
}
|
|
|
|
func TestVerifyPassword_CorrectPassword(t *testing.T) {
|
|
hash := hashPepper("admin", "secret")
|
|
users := map[string]string{"admin": hash}
|
|
|
|
if !auth.VerifyPassword(users, "admin", "secret") {
|
|
t.Error("expected password to match")
|
|
}
|
|
}
|
|
|
|
func TestVerifyPassword_WrongPassword(t *testing.T) {
|
|
hash := hashPepper("admin", "secret")
|
|
users := map[string]string{"admin": hash}
|
|
|
|
if auth.VerifyPassword(users, "admin", "wrong") {
|
|
t.Error("expected password to not match")
|
|
}
|
|
}
|
|
|
|
func TestVerifyPassword_UnknownUser(t *testing.T) {
|
|
if auth.VerifyPassword(map[string]string{}, "nobody", "secret") {
|
|
t.Error("expected false for unknown user")
|
|
}
|
|
}
|
|
|
|
func TestGenerateAndValidateJWT(t *testing.T) {
|
|
token, err := auth.GenerateJWT("admin", "testsecret")
|
|
if err != nil {
|
|
t.Fatalf("GenerateJWT: %v", err)
|
|
}
|
|
if token == "" {
|
|
t.Error("expected non-empty token")
|
|
}
|
|
|
|
claims, err := auth.ValidateJWT(token, "testsecret")
|
|
if err != nil {
|
|
t.Fatalf("ValidateJWT: %v", err)
|
|
}
|
|
if claims.Username != "admin" {
|
|
t.Errorf("expected username 'admin', got %q", claims.Username)
|
|
}
|
|
}
|
|
|
|
func TestValidateJWT_WrongSecret(t *testing.T) {
|
|
token, _ := auth.GenerateJWT("admin", "correct")
|
|
if _, err := auth.ValidateJWT(token, "wrong"); err == nil {
|
|
t.Error("expected error for wrong secret")
|
|
}
|
|
}
|
|
|
|
func TestValidateJWT_MalformedToken(t *testing.T) {
|
|
if _, err := auth.ValidateJWT("not.a.token", "secret"); err == nil {
|
|
t.Error("expected error for malformed token")
|
|
}
|
|
}
|