feat(auth): add MINDA_DEMO support with auto-seeded demo accounts and username login
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s

This commit is contained in:
sandrodach committed 2026-10-25 10:12:13 +01:00
1 parent 41c64c4924
commit 7f312fef14
12 files changed
+297 -23

No files matched your search

+18 -2
View File
@@ -301,6 +301,7 @@ func (s *Server) handleAuthStatus(w http.ResponseWriter, r *http.Request) {
"needsSetup": count == 0,
"allowRegistration": allowReg,
"instanceName": instanceName,
"demoMode": s.cfg.DemoMode,
})
}
@@ -451,6 +452,8 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
var req struct {
Email string `json:"email"`
Username string `json:"username"`
Login string `json:"login"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -458,9 +461,22 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
return
}
user, err := s.store.GetUserByEmail(req.Email)
identifier := strings.TrimSpace(req.Login)
if identifier == "" {
identifier = strings.TrimSpace(req.Username)
}
if identifier == "" {
identifier = strings.TrimSpace(req.Email)
}
if identifier == "" || req.Password == "" {
writeError(w, http.StatusBadRequest, "Benutzername/E-Mail und Passwort erforderlich")
return
}
user, err := s.store.GetUserByLogin(identifier)
if err != nil || !auth.CheckPassword(req.Password, user.PasswordHash) {
writeError(w, http.StatusUnauthorized, "E-Mail oder Passwort ungültig")
writeError(w, http.StatusUnauthorized, "Benutzername/E-Mail oder Passwort ungültig")
return
}
if !user.IsActive {
+87
View File
@@ -431,3 +431,90 @@ func TestNewFullFeatures(t *testing.T) {
t.Fatalf("expected updated kanbanStatus and notes, got %+v", updatedThought)
}
}
func TestLoginWithUsernameAndDemoMode(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "demo_api_test.db")
store, err := storage.NewStore(dbPath)
if err != nil {
t.Fatalf("failed to create store: %v", err)
}
defer store.Close()
if err := store.EnsureDemoAccounts(); err != nil {
t.Fatalf("failed to seed demo accounts: %v", err)
}
cfg := &config.Config{
Port: "8080",
DatabasePath: dbPath,
JWTSecret: "demo-jwt-secret",
DemoMode: true,
}
srv := NewServer(cfg, store, ws.NewHub(), nil)
router := srv.Router()
// 1. Check auth status reports demoMode: true
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/status", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200 OK from /api/v1/auth/status, got %d", rec.Code)
}
var statusResp struct {
NeedsSetup bool `json:"needsSetup"`
DemoMode bool `json:"demoMode"`
}
_ = json.Unmarshal(rec.Body.Bytes(), &statusResp)
if !statusResp.DemoMode {
t.Fatalf("expected demoMode=true, got %v", statusResp.DemoMode)
}
if statusResp.NeedsSetup {
t.Fatalf("expected needsSetup=false when demo accounts exist, got %v", statusResp.NeedsSetup)
}
// 2. Login with username "admin" / "admin"
loginBody := []byte(`{"email":"admin","password":"admin"}`)
req = httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(loginBody))
req.Header.Set("Content-Type", "application/json")
rec = httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200 OK for admin login, got %d: %s", rec.Code, rec.Body.String())
}
var loginRes struct {
Token string `json:"token"`
User domain.User `json:"user"`
}
_ = json.Unmarshal(rec.Body.Bytes(), &loginRes)
if loginRes.Token == "" || loginRes.User.Role != "admin" {
t.Fatalf("invalid admin login response: %+v", loginRes)
}
// 3. Login with username "user" / "user"
userLoginBody := []byte(`{"username":"user","password":"user"}`)
req = httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(userLoginBody))
req.Header.Set("Content-Type", "application/json")
rec = httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200 OK for user login, got %d: %s", rec.Code, rec.Body.String())
}
var userLoginRes struct {
Token string `json:"token"`
User domain.User `json:"user"`
}
_ = json.Unmarshal(rec.Body.Bytes(), &userLoginRes)
if userLoginRes.Token == "" || userLoginRes.User.Role != "member" {
t.Fatalf("invalid user login response: %+v", userLoginRes)
}
// 4. Invalid credentials must return 401
wrongLoginBody := []byte(`{"login":"admin","password":"wrongpassword"}`)
req = httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(wrongLoginBody))
req.Header.Set("Content-Type", "application/json")
rec = httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Fatalf("expected 401 Unauthorized for wrong password, got %d", rec.Code)
}
}