package webserver import ( "net/http" "strings" "github.com/go-chi/chi/v5" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func (ws *WebServer) handleUser(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Redirect(w, r, "/dashboard", http.StatusFound) return } data := ws.baseData(r, info) data["MountName"] = mountName // Try to fetch the caller's own key. if keyInfo, err := ws.vault.GetUserPublicKey(r.Context(), token, mountName, info.Username); err == nil { data["OwnKey"] = keyInfo } if users, err := ws.vault.ListUsers(r.Context(), token, mountName); err == nil { data["Users"] = users } ws.renderTemplate(w, "user.html", data) } func (ws *WebServer) handleUserRegister(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Error(w, "no user engine mounted", http.StatusNotFound) return } if _, err := ws.vault.UserRegister(r.Context(), token, mountName); err != nil { ws.renderUserWithError(w, r, mountName, info, grpcMessage(err)) return } http.Redirect(w, r, "/user", http.StatusFound) } func (ws *WebServer) handleUserProvision(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) if !info.IsAdmin { http.Error(w, "forbidden", http.StatusForbidden) return } token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Error(w, "no user engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() username := strings.TrimSpace(r.FormValue("username")) if username == "" { ws.renderUserWithError(w, r, mountName, info, "Username is required") return } if _, err := ws.vault.UserProvision(r.Context(), token, mountName, username); err != nil { ws.renderUserWithError(w, r, mountName, info, grpcMessage(err)) return } http.Redirect(w, r, "/user", http.StatusFound) } func (ws *WebServer) handleUserKeyDetail(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Error(w, "no user engine mounted", http.StatusNotFound) return } username := chi.URLParam(r, "username") keyInfo, err := ws.vault.GetUserPublicKey(r.Context(), token, mountName, username) if err != nil { st, _ := status.FromError(err) if st.Code() == codes.NotFound { http.Error(w, "user not found", http.StatusNotFound) return } http.Error(w, grpcMessage(err), http.StatusInternalServerError) return } data := ws.baseData(r, info) data["MountName"] = mountName data["KeyInfo"] = keyInfo ws.renderTemplate(w, "user_key_detail.html", data) } func (ws *WebServer) handleUserEncrypt(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Error(w, "no user engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() var recipients []string for _, line := range strings.Split(r.FormValue("recipients"), "\n") { if v := strings.TrimSpace(line); v != "" { recipients = append(recipients, v) } } plaintext := r.FormValue("plaintext") metadata := r.FormValue("metadata") if len(recipients) == 0 || plaintext == "" { ws.renderUserWithError(w, r, mountName, info, "Recipients and plaintext are required") return } envelope, err := ws.vault.UserEncrypt(r.Context(), token, mountName, plaintext, metadata, recipients) if err != nil { ws.renderUserWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderUserWithResult(w, r, mountName, info, "EncryptResult", envelope) } func (ws *WebServer) handleUserDecrypt(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Error(w, "no user engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() envelope := r.FormValue("envelope") if envelope == "" { ws.renderUserWithError(w, r, mountName, info, "Envelope is required") return } result, err := ws.vault.UserDecrypt(r.Context(), token, mountName, envelope) if err != nil { ws.renderUserWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderUserWithResult(w, r, mountName, info, "DecryptResult", result) } func (ws *WebServer) handleUserReEncrypt(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Error(w, "no user engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() envelope := r.FormValue("envelope") if envelope == "" { ws.renderUserWithError(w, r, mountName, info, "Envelope is required") return } newEnvelope, err := ws.vault.UserReEncrypt(r.Context(), token, mountName, envelope) if err != nil { ws.renderUserWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderUserWithResult(w, r, mountName, info, "ReEncryptResult", newEnvelope) } func (ws *WebServer) handleUserRotateKey(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Error(w, "no user engine mounted", http.StatusNotFound) return } if _, err := ws.vault.UserRotateKey(r.Context(), token, mountName); err != nil { ws.renderUserWithError(w, r, mountName, info, grpcMessage(err)) return } http.Redirect(w, r, "/user", http.StatusSeeOther) } func (ws *WebServer) handleUserDeleteUser(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) if !info.IsAdmin { http.Error(w, "forbidden", http.StatusForbidden) return } token := extractCookie(r) mountName, err := ws.findUserMount(r, token) if err != nil { http.Error(w, "no user engine mounted", http.StatusNotFound) return } username := chi.URLParam(r, "username") if err := ws.vault.UserDeleteUser(r.Context(), token, mountName, username); err != nil { http.Error(w, grpcMessage(err), http.StatusInternalServerError) return } http.Redirect(w, r, "/user", http.StatusSeeOther) } func (ws *WebServer) renderUserWithError(w http.ResponseWriter, r *http.Request, mountName string, info *TokenInfo, errMsg string) { token := extractCookie(r) data := ws.baseData(r, info) data["MountName"] = mountName data["Error"] = errMsg if keyInfo, err := ws.vault.GetUserPublicKey(r.Context(), token, mountName, info.Username); err == nil { data["OwnKey"] = keyInfo } if users, err := ws.vault.ListUsers(r.Context(), token, mountName); err == nil { data["Users"] = users } ws.renderTemplate(w, "user.html", data) } func (ws *WebServer) renderUserWithResult(w http.ResponseWriter, r *http.Request, mountName string, info *TokenInfo, resultKey string, result interface{}) { token := extractCookie(r) data := ws.baseData(r, info) data["MountName"] = mountName data[resultKey] = result if keyInfo, err := ws.vault.GetUserPublicKey(r.Context(), token, mountName, info.Username); err == nil { data["OwnKey"] = keyInfo } if users, err := ws.vault.ListUsers(r.Context(), token, mountName); err == nil { data["Users"] = users } ws.renderTemplate(w, "user.html", data) }