package ui import ( "fmt" "net/http" "strings" "git.wntrmute.dev/mc/mcias/internal/audit" "git.wntrmute.dev/mc/mcias/internal/model" ) func (u *UIServer) handleSSOClientsPage(w http.ResponseWriter, r *http.Request) { csrfToken, err := u.setCSRFCookies(w) if err != nil { u.logger.Error("sso-clients: set CSRF cookies", "error", err) http.Error(w, "internal error", http.StatusInternalServerError) return } clients, err := u.db.ListSSOClients() if err != nil { u.logger.Error("sso-clients: list clients", "error", err) u.renderError(w, r, http.StatusInternalServerError, "failed to load SSO clients") return } u.render(w, "sso_clients", SSOClientsData{ PageData: PageData{CSRFToken: csrfToken, ActorName: u.actorName(r), IsAdmin: isAdmin(r)}, Clients: clients, }) } func (u *UIServer) handleCreateSSOClientUI(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxFormBytes) if err := r.ParseForm(); err != nil { u.renderError(w, r, http.StatusBadRequest, "invalid form submission") return } clientID := strings.TrimSpace(r.FormValue("client_id")) redirectURI := strings.TrimSpace(r.FormValue("redirect_uri")) tagsStr := strings.TrimSpace(r.FormValue("tags")) if clientID == "" || redirectURI == "" { u.renderError(w, r, http.StatusBadRequest, "client_id and redirect_uri are required") return } var tags []string if tagsStr != "" { for _, t := range strings.Split(tagsStr, ",") { t = strings.TrimSpace(t) if t != "" { tags = append(tags, t) } } } claims := claimsFromContext(r.Context()) var actorID *int64 if claims != nil { if acct, err := u.db.GetAccountByUUID(claims.Subject); err == nil { actorID = &acct.ID } } c, err := u.db.CreateSSOClient(clientID, redirectURI, tags, actorID) if err != nil { u.renderError(w, r, http.StatusBadRequest, err.Error()) return } u.writeAudit(r, model.EventSSOClientCreated, actorID, nil, audit.JSON("client_id", c.ClientID)) u.render(w, "sso_client_row", c) } func (u *UIServer) handleToggleSSOClient(w http.ResponseWriter, r *http.Request) { clientID := r.PathValue("clientId") r.Body = http.MaxBytesReader(w, r.Body, maxFormBytes) if err := r.ParseForm(); err != nil { u.renderError(w, r, http.StatusBadRequest, "invalid form") return } enabled := r.FormValue("enabled") == "true" if err := u.db.UpdateSSOClient(clientID, nil, nil, &enabled); err != nil { u.renderError(w, r, http.StatusInternalServerError, "failed to update SSO client") return } claims := claimsFromContext(r.Context()) var actorID *int64 if claims != nil { if acct, err := u.db.GetAccountByUUID(claims.Subject); err == nil { actorID = &acct.ID } } u.writeAudit(r, model.EventSSOClientUpdated, actorID, nil, fmt.Sprintf(`{"client_id":%q,"enabled":%v}`, clientID, enabled)) c, err := u.db.GetSSOClient(clientID) if err != nil { u.renderError(w, r, http.StatusInternalServerError, "failed to reload SSO client") return } u.render(w, "sso_client_row", c) } func (u *UIServer) handleDeleteSSOClientUI(w http.ResponseWriter, r *http.Request) { clientID := r.PathValue("clientId") if err := u.db.DeleteSSOClient(clientID); err != nil { u.renderError(w, r, http.StatusInternalServerError, "failed to delete SSO client") return } claims := claimsFromContext(r.Context()) var actorID *int64 if claims != nil { if acct, err := u.db.GetAccountByUUID(claims.Subject); err == nil { actorID = &acct.ID } } u.writeAudit(r, model.EventSSOClientDeleted, actorID, nil, audit.JSON("client_id", clientID)) // Return empty response so HTMX removes the row. w.WriteHeader(http.StatusOK) }