ThatAIguyA world of ideas

THE CONSCIOUSNESS ATLAS
Read. Experiment. Follow the evidence.

ConsciousnessLearn · Explore · Joseph Anady

Janady13/missionmatch / internal/modules/org/teams_handler.go

Source identity, revision, and recovered contents.

GitHub sourceSource text recovered

Source identity

FieldRecorded value
Source typeGitHub source
Original recordJanady13/missionmatch / internal/modules/org/teams_handler.go
DateNot specified in source
RepositoryJanady13/missionmatch
Revision720d47fae476b0d38477ae4401697d372b1ea2bc
Branchfeature/opportunity-to-outcome
Path or archive memberinternal/modules/org/teams_handler.go
Original SHA-256a1aaf4e691e43b10acc4648c5337e5a3ae59d8a3b963fd8994ac1e2eb8b87e09
Git blob SHA798ec3dfd5da8abe7d6fa75786c733de4dcd1a54
Reading-copy SHA-256a1aaf4e691e43b10acc4648c5337e5a3ae59d8a3b963fd8994ac1e2eb8b87e09

Original source ↗

Recovered source transcript

package org // TEAMS, the unit a desk belongs to. // // Team scope in migration 0088 means "records owned by anyone who shares a team with me", so without a way to // create a team and put people on it, that scope resolves to the caller alone and the setting is a trap. This // is the surface that makes it real. // // A PERSON MAY BE ON MORE THAN ONE TEAM, deliberately. Agencies run people across desks, and a model that // forces one team per person makes the common case a lie. Team scope is therefore the union of every team the // caller belongs to. // // TEAM LEAD IS A LABEL, NOT AN AUTHORITY. Making somebody a lead does not promote them: what they may do is // still decided by the role ladder and the capability matrix. This is the mistake that turns an org chart // feature into a privilege escalation path, and it is avoided by simply not wiring the lead to anything. import ( "context" "encoding/json" "errors" "net/http" "strings" "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/missionworkforce/missionmatch/internal/platform/audit" "github.com/missionworkforce/missionmatch/internal/platform/security" "github.com/missionworkforce/missionmatch/internal/platform/tenant" ) type TeamsHandler struct { db *tenant.DB aud *audit.Writer } func NewTeamsHandler(db *tenant.DB, aud *audit.Writer) *TeamsHandler { return &TeamsHandler{db: db, aud: aud} } func (h *TeamsHandler) Routes(r chi.Router) { // READING the desks is open to everyone who can read a candidate. Knowing which desk a colleague sits on // is org chart information, not privileged data, and hiding it would make the scope setting inexplicable // to the people it applies to. read := security.RequireCapability(security.CapCandidateRead) // WRITING is org.settings.write, the owner tier. Team membership decides what people can see once record // scope is narrowed, so adding somebody to a team is an access grant and belongs at the same authority as // the setting that gives it meaning. write := security.RequireCapability(security.CapOrgSettings) r.With(read).Get("/teams", h.list) r.With(read).Get("/teams/mine", h.mine) r.With(write).Post("/teams", h.create) r.With(write).Patch("/teams/{teamID}", h.update) r.With(write).Post("/teams/{teamID}/members", h.addMember) r.With(write).Delete("/teams/{teamID}/members/{userID}", h.removeMember) } type teamMember struct { UserID string `json:"user_id"` Name string `json:"name"` Email string `json:"email"` Role string `json:"role"` IsLead bool `json:"is_lead"` Disabled bool `json:"disabled"` } type team struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` LeadID string `json:"lead_user_id,omitempty"` LeadName string `json:"lead_name,omitempty"` Members []teamMember `json:"members"` } func (h *TeamsHandler) loadTeams(ctx context.Context, tx pgx.Tx, onlyUser *uuid.UUID) ([]team, error) { q := ` SELECT t.id::text, t.name, t.status, coalesce(t.lead_user_id::text,''), coalesce(l.full_name,'') FROM teams t LEFT JOIN users l ON l.id = t.lead_user_id WHERE ($1::uuid IS NULL OR EXISTS ( SELECT 1 FROM team_members m WHERE m.team_id = t.id AND m.user_id = $1)) ORDER BY t.status, lower(t.name)` // A TYPED pointer, not an untyped nil in an any. pgx cannot infer a parameter type from a nil interface, // so the untyped form made every read of this route a 500 while the writes worked perfectly, which is // exactly the asymmetry that made it invisible until a recruiter tried to look at the desks. rows, err := tx.Query(ctx, q, onlyUser) if err != nil { return nil, err } defer rows.Close() out := make([]team, 0, 16) idx := map[string]int{} for rows.Next() { var t team if err := rows.Scan(&t.ID, &t.Name, &t.Status, &t.LeadID, &t.LeadName); err != nil { return nil, err } t.Members = []teamMember{} idx[t.ID] = len(out) out = append(out, t) } if rows.Err() != nil { return nil, rows.Err() } if len(out) == 0 { return out, nil } // Members are loaded in ONE query rather than per team, so a floor with thirty desks is two round trips // and not thirty one. mrows, err := tx.Query(ctx, ` SELECT m.team_id::text, m.user_id::text, coalesce(u.full_name,''), coalesce(u.email,''), coalesce(u.role,''), -- COALESCED TO false. A team with no lead makes "lead_user_id = user_id" NULL rather than -- false, and scanning a NULL into a Go bool is an error, so a desk without a named lead made -- the whole read a 500 while every write kept working. That asymmetry is why it survived -- until somebody tried to look at the desks. coalesce(t.lead_user_id = m.user_id, false), coalesce(u.status <> 'active', false) FROM team_members m JOIN teams t ON t.id = m.team_id LEFT JOIN users u ON u.id = m.user_id ORDER BY lower(coalesce(u.full_name,''))`) if err != nil { return nil, err } defer mrows.Close() for mrows.Next() { var tid string var m teamMember if err := mrows.Scan(&tid, &m.UserID, &m.Name, &m.Email, &m.Role, &m.IsLead, &m.Disabled); err != nil { return nil, err } if i, ok := idx[tid]; ok { out[i].Members = append(out[i].Members, m) } } return out, mrows.Err() } func (h *TeamsHandler) list(w http.ResponseWriter, r *http.Request) { var out []team err := h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { var e error out, e = h.loadTeams(ctx, tx, nil) return e }) if err != nil { writeErr(w, http.StatusInternalServerError, "could not read teams") return } writeJSON(w, http.StatusOK, map[string]any{"teams": out, "count": len(out)}) } func (h *TeamsHandler) mine(w http.ResponseWriter, r *http.Request) { actor := actorUUID(r.Context()) if actor == uuid.Nil { writeErr(w, http.StatusForbidden, "no principal") return } var out []team err := h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { var e error out, e = h.loadTeams(ctx, tx, &actor) return e }) if err != nil { writeErr(w, http.StatusInternalServerError, "could not read teams") return } writeJSON(w, http.StatusOK, map[string]any{"teams": out, "count": len(out)}) } type teamRequest struct { Name string `json:"name"` LeadUserID *string `json:"lead_user_id"` Status *string `json:"status"` } func (h *TeamsHandler) create(w http.ResponseWriter, r *http.Request) { var in teamRequest if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<10)).Decode(&in); err != nil { writeErr(w, http.StatusBadRequest, "invalid body") return } in.Name = strings.TrimSpace(in.Name) if in.Name == "" || len(in.Name) > 120 { writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "a team needs a name of up to 120 characters", "field": "name"}) return } lead, ok := parseOptionalUUID(w, in.LeadUserID) if !ok { return } actor := actorUUID(r.Context()) var id uuid.UUID err := h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { e := tx.QueryRow(ctx, ` INSERT INTO teams (organization_id, name, lead_user_id) VALUES (NULLIF(current_setting('app.current_org_id', true), '')::uuid, $1, $2) RETURNING id`, in.Name, lead).Scan(&id) if e != nil { return e } // The lead is added as a member automatically. A lead who is not on their own team would be excluded // from their own desk's records the moment scope is narrowed, which nobody would ever intend. if lead != nil { if _, e := tx.Exec(ctx, ` INSERT INTO team_members (organization_id, team_id, user_id, added_by) VALUES (NULLIF(current_setting('app.current_org_id', true), '')::uuid, $1, $2, $3) ON CONFLICT DO NOTHING`, id, *lead, nullUUID(actor)); e != nil { return e } } return h.auditTeam(ctx, tx, r, actor, "org.team.created", id, map[string]any{"name": in.Name}) }) if err != nil { if isUnique(err) { writeErr(w, http.StatusConflict, "a team with that name already exists") return } writeErr(w, http.StatusInternalServerError, "could not create the team") return } writeJSON(w, http.StatusCreated, map[string]any{"id": id.String(), "name": in.Name}) } func (h *TeamsHandler) update(w http.ResponseWriter, r *http.Request) { id, ok := parsePathUUID(w, r, "teamID") if !ok { return } var in teamRequest if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<10)).Decode(&in); err != nil { writeErr(w, http.StatusBadRequest, "invalid body") return } if in.Status != nil && *in.Status != "active" && *in.Status != "archived" { writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "a team is either active or archived", "field": "status"}) return } lead, ok := parseOptionalUUID(w, in.LeadUserID) if !ok { return } actor := actorUUID(r.Context()) err := h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { set := []string{"updated_at = now()"} args := []any{id} if n := strings.TrimSpace(in.Name); n != "" { args = append(args, n) set = append(set, "name = $2") } if in.Status != nil { args = append(args, *in.Status) set = append(set, "status = $"+itoa(len(args))) } if in.LeadUserID != nil { args = append(args, lead) set = append(set, "lead_user_id = $"+itoa(len(args))) } tag, e := tx.Exec(ctx, `UPDATE teams SET `+strings.Join(set, ", ")+` WHERE id = $1`, args...) if e != nil { return e } if tag.RowsAffected() == 0 { return errNoTeam } return h.auditTeam(ctx, tx, r, actor, "org.team.updated", id, map[string]any{ "name": in.Name, "status": in.Status, }) }) if err != nil { h.failTeam(w, err) return } writeJSON(w, http.StatusOK, map[string]any{"updated": true}) } func (h *TeamsHandler) addMember(w http.ResponseWriter, r *http.Request) { id, ok := parsePathUUID(w, r, "teamID") if !ok { return } var in struct { UserID string `json:"user_id"` } if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 2<<10)).Decode(&in); err != nil { writeErr(w, http.StatusBadRequest, "invalid body") return } member, err := uuid.Parse(strings.TrimSpace(in.UserID)) if err != nil { writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "a user identifier is required", "field": "user_id"}) return } actor := actorUUID(r.Context()) err = h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { // The composite foreign keys already refuse a team or a user from another tenant, and RLS means a // row from elsewhere is simply not there. This exists so the caller gets a sentence rather than a // constraint violation. var one int if e := tx.QueryRow(ctx, `SELECT 1 FROM teams WHERE id = $1`, id).Scan(&one); e != nil { if errors.Is(e, pgx.ErrNoRows) { return errNoTeam } return e } if e := tx.QueryRow(ctx, `SELECT 1 FROM users WHERE id = $1`, member).Scan(&one); e != nil { if errors.Is(e, pgx.ErrNoRows) { return errNoUser } return e } if _, e := tx.Exec(ctx, ` INSERT INTO team_members (organization_id, team_id, user_id, added_by) VALUES (NULLIF(current_setting('app.current_org_id', true), '')::uuid, $1, $2, $3) ON CONFLICT DO NOTHING`, id, member, nullUUID(actor)); e != nil { return e } // AUDITED AS A PERMISSION CHANGE, because under a narrowed record scope that is exactly what it is: // this line decides which records the person can see. return h.auditTeam(ctx, tx, r, actor, "org.team.member_added", id, map[string]any{"user_id": member.String()}) }) if err != nil { h.failTeam(w, err) return } writeJSON(w, http.StatusOK, map[string]any{"added": true}) } func (h *TeamsHandler) removeMember(w http.ResponseWriter, r *http.Request) { id, ok := parsePathUUID(w, r, "teamID") if !ok { return } member, err := uuid.Parse(chi.URLParam(r, "userID")) if err != nil { writeErr(w, http.StatusBadRequest, "that is not a user identifier") return } actor := actorUUID(r.Context()) err = h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { if _, e := tx.Exec(ctx, `DELETE FROM team_members WHERE team_id = $1 AND user_id = $2`, id, member); e != nil { return e } // Removing the lead from the team clears the lead, so a team cannot be led by somebody who is not on // it. Left alone, that state would render as a lead name on a desk they have no access to. if _, e := tx.Exec(ctx, `UPDATE teams SET lead_user_id = NULL WHERE id = $1 AND lead_user_id = $2`, id, member); e != nil { return e } return h.auditTeam(ctx, tx, r, actor, "org.team.member_removed", id, map[string]any{"user_id": member.String()}) }) if err != nil { h.failTeam(w, err) return } writeJSON(w, http.StatusOK, map[string]any{"removed": true}) } var ( errNoTeam = errors.New("org: no such team") errNoUser = errors.New("org: no such user") ) func (h *TeamsHandler) failTeam(w http.ResponseWriter, err error) { switch { case errors.Is(err, errNoTeam): writeErr(w, http.StatusNotFound, "no such team") case errors.Is(err, errNoUser): writeErr(w, http.StatusNotFound, "no such user in this organization") case isUnique(err): writeErr(w, http.StatusConflict, "a team with that name already exists") default: writeErr(w, http.StatusInternalServerError, "could not update the team") } } func (h *TeamsHandler) auditTeam(ctx context.Context, tx pgx.Tx, r *http.Request, actor uuid.UUID, action string, id uuid.UUID, body map[string]any) error { if h.aud == nil { return nil } b, _ := json.Marshal(body) return h.aud.OnTx(ctx, tx, audit.Event{ Action: action, ActorUserID: &actor, EntityType: "team", EntityID: id.String(), NewValues: b, IPAddress: r.Header.Get("X-Forwarded-For"), UserAgent: r.UserAgent(), }) } func parsePathUUID(w http.ResponseWriter, r *http.Request, name string) (uuid.UUID, bool) { id, err := uuid.Parse(chi.URLParam(r, name)) if err != nil { writeErr(w, http.StatusBadRequest, "that is not an identifier") return uuid.Nil, false } return id, true } func parseOptionalUUID(w http.ResponseWriter, s *string) (*uuid.UUID, bool) { if s == nil || strings.TrimSpace(*s) == "" { return nil, true } id, err := uuid.Parse(strings.TrimSpace(*s)) if err != nil { writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "that is not a user identifier", "field": "lead_user_id"}) return nil, false } return &id, true } func nullUUID(u uuid.UUID) any { if u == uuid.Nil { return nil } return u } func isUnique(err error) bool { m := err.Error() return strings.Contains(m, "duplicate key") || strings.Contains(m, "unique constraint") } func itoa(n int) string { if n < 10 { return string(rune('0' + n)) } return string(rune('0'+n/10)) + string(rune('0'+n%10)) }

Search equations, builds, manuscript text, and recovered sources.