package api import ( "net/http" "markdownhub/internal/auth" ) func (s *Server) handleTOTPSetup(w http.ResponseWriter, r *http.Request) { userID := getUserID(r) secret := auth.GenerateTOTPSecret() var email string s.db.QueryRow("SELECT email FROM users WHERE id = ?", userID).Scan(&email) uri := auth.TOTPUri(secret, email, "MarkdownHub") // Store in pending column — not active until verified s.db.Exec("UPDATE users SET totp_pending = ? WHERE id = ?", secret, userID) writeJSON(w, 200, map[string]string{ "secret": secret, "uri": uri, }) } func (s *Server) handleTOTPVerify(w http.ResponseWriter, r *http.Request) { var req struct { Code string `json:"code"` } if err := decodeBody(r, &req); err != nil || req.Code == "" { writeJSON(w, 400, map[string]string{"error": "code required"}) return } userID := getUserID(r) var pending *string s.db.QueryRow("SELECT totp_pending FROM users WHERE id = ?", userID).Scan(&pending) if pending == nil || *pending == "" { writeJSON(w, 400, map[string]string{"error": "TOTP not set up — run setup first"}) return } if !auth.ValidateTOTP(*pending, req.Code) { writeJSON(w, 401, map[string]string{"error": "invalid code"}) return } // Code valid — promote pending to active s.db.Exec("UPDATE users SET totp_secret = ?, totp_pending = NULL WHERE id = ?", *pending, userID) writeJSON(w, 200, map[string]string{"status": "verified"}) } func (s *Server) handleTOTPDisable(w http.ResponseWriter, r *http.Request) { var req struct { Code string `json:"code"` } if err := decodeBody(r, &req); err != nil || req.Code == "" { writeJSON(w, 400, map[string]string{"error": "code required"}) return } userID := getUserID(r) var secret *string s.db.QueryRow("SELECT totp_secret FROM users WHERE id = ?", userID).Scan(&secret) if secret == nil || *secret == "" { writeJSON(w, 400, map[string]string{"error": "2FA not enabled"}) return } if !auth.ValidateTOTP(*secret, req.Code) { writeJSON(w, 401, map[string]string{"error": "invalid code"}) return } s.db.Exec("UPDATE users SET totp_secret = NULL, totp_pending = NULL WHERE id = ?", userID) writeJSON(w, 200, map[string]string{"status": "disabled"}) }