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
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
This commit is contained in:
1 parent
41c64c4924
commit
7f312fef14
12 files changed
+297
-23
No files matched your search
+18
-2
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user