Complete TODO items: security, features, polish
Security: - Encrypt Gitea tokens at rest (AES-256-GCM with MH_SECRET) - Secure cookie flag when behind HTTPS (X-Forwarded-Proto) - Password complexity (min 8 chars) - TOTP: defer persist until verified (totp_pending column) - Audit log table + logging on login/rename/password change Features: - Rename files/folders (double-click in tree, /api/files/rename) - beforeunload warning for unsaved changes - Mobile hamburger menu - PWA icons (192px, 512px) - Max file size enforcement (10MB) - Shared file read access (cross-user with permission check) Polish: - Toast notifications replace all alert() calls - Keyboard shortcut help overlay (Ctrl+/) - File rename via double-click in FileTree
This commit is contained in:
+16
-12
@@ -9,17 +9,15 @@ import (
|
||||
func (s *Server) handleTOTPSetup(w http.ResponseWriter, r *http.Request) {
|
||||
userID := getUserID(r)
|
||||
|
||||
// Generate secret
|
||||
secret := auth.GenerateTOTPSecret()
|
||||
|
||||
// Get user email for URI
|
||||
var email string
|
||||
s.db.QueryRow("SELECT email FROM users WHERE id = ?", userID).Scan(&email)
|
||||
|
||||
uri := auth.TOTPUri(secret, email, "MarkdownHub")
|
||||
|
||||
// Store secret (not yet verified)
|
||||
s.db.Exec("UPDATE users SET totp_secret = ? WHERE id = ?", secret, userID)
|
||||
// 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,
|
||||
@@ -37,18 +35,20 @@ func (s *Server) handleTOTPVerify(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
userID := getUserID(r)
|
||||
var secret string
|
||||
s.db.QueryRow("SELECT totp_secret FROM users WHERE id = ?", userID).Scan(&secret)
|
||||
if secret == "" {
|
||||
writeJSON(w, 400, map[string]string{"error": "TOTP not set up"})
|
||||
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(secret, req.Code) {
|
||||
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"})
|
||||
}
|
||||
|
||||
@@ -62,14 +62,18 @@ func (s *Server) handleTOTPDisable(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
userID := getUserID(r)
|
||||
var secret string
|
||||
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) {
|
||||
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 WHERE id = ?", userID)
|
||||
s.db.Exec("UPDATE users SET totp_secret = NULL, totp_pending = NULL WHERE id = ?", userID)
|
||||
writeJSON(w, 200, map[string]string{"status": "disabled"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user