Files
anders 68eaee0b9f 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
2026-05-26 23:51:02 +02:00

68 lines
1.6 KiB
Go

package api
import (
"context"
"net/http"
"strings"
"markdownhub/internal/auth"
)
type contextKey string
const (
ctxUserID contextKey = "userID"
ctxIsAdmin contextKey = "isAdmin"
)
func (s *Server) requireAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tokenStr := extractToken(r)
if tokenStr == "" {
http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
return
}
userID, isAdmin, err := auth.ValidateToken(tokenStr, s.secret)
if err != nil || userID == "" {
http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), ctxUserID, userID)
ctx = context.WithValue(ctx, ctxIsAdmin, isAdmin)
next(w, r.WithContext(ctx))
}
}
func (s *Server) requireAdmin(next http.HandlerFunc) http.HandlerFunc {
return s.requireAuth(func(w http.ResponseWriter, r *http.Request) {
isAdmin, _ := r.Context().Value(ctxIsAdmin).(bool)
if !isAdmin {
http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
return
}
next(w, r)
})
}
func extractToken(r *http.Request) string {
// Cookie first
if c, err := r.Cookie("authToken"); err == nil && c.Value != "" {
return c.Value
}
// Bearer header
h := r.Header.Get("Authorization")
if strings.HasPrefix(h, "Bearer ") {
return h[7:]
}
return ""
}
func getUserID(r *http.Request) string {
v, _ := r.Context().Value(ctxUserID).(string)
return v
}
func (s *Server) audit(userID, action, detail string) {
s.db.Exec("INSERT INTO audit_log (user_id, action, detail) VALUES (?, ?, ?)", userID, action, detail)
}