ThatAIguyA world of ideas

THE CONSCIOUSNESS ATLAS
Read. Experiment. Follow the evidence.

ConsciousnessLearn · Explore · Joseph Anady

Janady13/missionmatch / internal/modules/org/settings_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/settings_handler.go
DateNot specified in source
RepositoryJanady13/missionmatch
Revision720d47fae476b0d38477ae4401697d372b1ea2bc
Branchfeature/opportunity-to-outcome
Path or archive memberinternal/modules/org/settings_handler.go
Original SHA-2569867b47bdb6e650cbbdbacf485f0155ac5f111072b89c06557dc454447904ad8
Git blob SHAc4fce1234b9dba26e1acd3f3f4e7e1123653282d
Reading-copy SHA-25621434fe0495336c6799dea9fe2a4ae53f4c61a2ca1694d045590bc83c650ccae

Original source ↗

Recovered source transcript

Credential-bearing and unrelated personal details were removed from this reading copy. The source identity and original checksum remain recorded.

package org // THE SETTINGS SURFACE, and the reason it lives beside branding rather than in a module of its own: this is // the same authority (org.settings.write, held by the owner) over the same subject (how this organization // works), and splitting it would have meant a second module importing the first for no gain. // // TWO AUDIENCES, TWO ROUTES, AND THE DIFFERENCE IS LOAD BEARING. // // GET /org/settings is the ADMINISTRATOR'S view: every configured value, gated on org.settings.write. It // still contains no credential, because the settings type cannot hold one. // // GET /org/policy is EVERY USER'S view, gated only on being able to read a candidate. It returns no settings // at all. It returns the ANSWERS, already computed for the caller: may I see rates, may I see who else is // working this. The interface needs those answers to render, and handing a recruiter the whole configuration // so their browser can work it out would both leak how the organization is set up and put the decision on // the client, where it is advice rather than enforcement. The server decides; the client is told. import ( "context" "encoding/json" "errors" "fmt" "net/http" "strings" "time" "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/shopspring/decimal" "github.com/missionworkforce/missionmatch/internal/platform/audit" "github.com/missionworkforce/missionmatch/internal/platform/mailer" "github.com/missionworkforce/missionmatch/internal/platform/orgsettings" "github.com/missionworkforce/missionmatch/internal/platform/security" "github.com/missionworkforce/missionmatch/internal/platform/tenant" ) // SettingsHandler serves the settings surface. store may be nil on a node built without it, in which case // every route reports itself unavailable rather than pretending to save. type SettingsHandler struct { db *tenant.DB store *orgsettings.Store aud *audit.Writer } func NewSettingsHandler(db *tenant.DB, store *orgsettings.Store, aud *audit.Writer) *SettingsHandler { return &SettingsHandler{db: db, store: store, aud: aud} } func (h *SettingsHandler) Routes(r chi.Router) { r.With(security.RequireCapability(security.CapCandidateRead)).Get("/org/policy", h.policy) r.With(security.RequireCapability(security.CapOrgSettings)).Get("/org/settings", h.get) r.With(security.RequireCapability(security.CapOrgSettings)).Patch("/org/settings", h.patch) r.With(security.RequireCapability(security.CapOrgSettings)).Put("/org/settings/email/password", h.putPassword) r.With(security.RequireCapability(security.CapOrgSettings)).Delete("/org/settings/email/password", h.deletePassword) r.With(security.RequireCapability(security.CapOrgSettings)).Post("/org/settings/email/test", h.testEmail) } func (h *SettingsHandler) get(w http.ResponseWriter, r *http.Request) { if h.store == nil { writeErr(w, http.StatusServiceUnavailable, "settings are unavailable on this node") return } st, err := h.store.Load(r.Context()) if err != nil { writeErr(w, http.StatusInternalServerError, "could not read settings") return } writeJSON(w, http.StatusOK, map[string]any{ "settings": st, // Told plainly, so an administrator is not asked to type a password this node cannot store. "secret_storage_available": h.store.SealAvailable(), }) } // policyResponse is what the interface renders against. Every field is a decision already made for THIS // caller, never a setting they could reinterpret. type policyResponse struct { CanSeeRates bool `json:"can_see_rates"` CanSeeMargins bool `json:"can_see_margins"` CanSeeContacts bool `json:"can_see_contacts"` CanSeeWorkOf bool `json:"can_see_work_of_others"` ShowPresence bool `json:"show_activity_presence"` ClaimRequired bool `json:"claim_required"` ClaimExpiryHrs int `json:"claim_expiry_hours"` EmailDelivery bool `json:"email_delivery_enabled"` // NoticeRequired says the organization refuses to score a candidate with no automated decision notice on // file. Every role is told, because it is a recruiter who meets the refusal and a recruiter who has to // know why before they meet it rather than after. NoticeRequired bool `json:"aedt_notice_required"` // TimeZone comes from organizations.time_zone, the column that has held it since migration 0057 and // already decides what "today" means for the task queue. It is reported here so a screen rendering a // deadline does not have to ask a second endpoint for it. TimeZone string `json:"time_zone"` // RecordScope is the caller's RESOLVED scope, not the organization's setting. The interface uses it to // explain a short list honestly, so a recruiter sees "showing your records" rather than concluding the // bench is empty. RecordScope string `json:"record_scope"` } func (h *SettingsHandler) policy(w http.ResponseWriter, r *http.Request) { if h.store == nil { writeErr(w, http.StatusServiceUnavailable, "settings are unavailable on this node") return } p, ok := tenant.UserFromContext(r.Context()) if !ok { writeErr(w, http.StatusForbidden, "no principal") return } st, err := h.store.Load(r.Context()) if err != nil { writeErr(w, http.StatusInternalServerError, "could not read settings") return } orgTimeZone := "America/New_York" _ = h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { return tx.QueryRow(ctx, `SELECT time_zone FROM organizations LIMIT 1`).Scan(&orgTimeZone) }) role := security.Role(p.Role) writeJSON(w, http.StatusOK, policyResponse{ CanSeeRates: security.CanSeeField(role, st.RateVisibility), CanSeeMargins: security.CanSeeField(role, st.MarginVisibility), CanSeeContacts: security.CanSeeField(role, st.ContactVisibility), CanSeeWorkOf: security.CanSeeOthersWork(role, st.WorkVisibility), ShowPresence: st.ShowActivityPresence, ClaimRequired: st.ClaimRequired, ClaimExpiryHrs: st.ClaimExpiryHours, EmailDelivery: st.EmailDeliveryEnabled && st.SMTPVerifiedAt != nil, TimeZone: orgTimeZone, RecordScope: string(security.ResolveRecordScope(role, st.StaffRecordScope, st.ManagerRecordScope)), NoticeRequired: st.AEDTNoticeRequired, }) } // patchRequest mirrors orgsettings.Update with JSON tags. Pointers throughout, so a field the administrator // did not touch is absent rather than zero, and a partial save cannot blank a colleague's setting. type patchRequest struct { EmailDeliveryEnabled *bool `json:"email_delivery_enabled"` EmailFromAddress *string `json:"email_from_address"` EmailFromName *string `json:"email_from_name"` EmailReplyTo *string `json:"email_reply_to"` SMTPHost *string `json:"smtp_host"` SMTPPort *int `json:"smtp_port"` SMTPUsername *string `json:"smtp_username"` SMTPSecurity *string `json:"smtp_security"` WorkVisibility *string `json:"work_visibility"` ShowActivityPresence *bool `json:"show_activity_presence"` ClaimRequired *bool `json:"claim_required"` ClaimExpiryHours *int `json:"claim_expiry_hours"` RateVisibility *string `json:"rate_visibility"` MarginVisibility *string `json:"margin_visibility"` ContactVisibility *string `json:"contact_visibility"` StaffRecordScope *string `json:"staff_record_scope"` ManagerRecordScope *string `json:"manager_record_scope"` UnownedRecordsVisible *bool `json:"unowned_records_visible"` InviteExpiryHours *int `json:"invite_expiry_hours"` DefaultInviteRole *string `json:"default_invite_role"` // DefaultBurdenPct arrives as a STRING, never a JSON number, for the same reason every other rate in this // product does: a wrap that round tripped through a float does not reconcile with an invoice. // // The double pointer is what carries the three meanings. Field absent, so the outer pointer is nil, means // leave the wrap alone. Field present as JSON null, so the outer is set and the inner is nil, means clear // it. Field present as a string sets it. Without the distinction, clearing and not touching look identical // on the wire and an owner cannot remove a wrap they entered by mistake. DefaultBurdenPct *string `json:"default_burden_pct"` // AEDTNoticeRequired refuses to score a candidate with no automated decision notice on file. AEDTNoticeRequired *bool `json:"aedt_notice_required"` // defaultBurdenPresent records whether the KEY appeared at all, which a *string cannot express: a JSON null // and an absent key both decode to nil. UnmarshalJSON below is the only place it is set. defaultBurdenPresent bool } // UnmarshalJSON exists ONLY to tell an absent default_burden_pct apart from an explicit null. // // encoding/json decodes both to a nil pointer, and the two mean opposite things here: one leaves the // organization's wrap untouched, the other erases it and takes every margin in the product with it. A settings // PATCH that sends a subset of fields, which is the normal case and is what the interface does, would // otherwise clear the wrap on every unrelated save. func (p *patchRequest) UnmarshalJSON(b []byte) error { type raw patchRequest var r raw if err := json.Unmarshal(b, &r); err != nil { return err } var keys map[string]json.RawMessage if err := json.Unmarshal(b, &keys); err != nil { return err } *p = patchRequest(r) _, p.defaultBurdenPresent = keys["default_burden_pct"] return nil } func (p patchRequest) toUpdate() orgsettings.Update { return orgsettings.Update{ EmailDeliveryEnabled: p.EmailDeliveryEnabled, EmailFromAddress: p.EmailFromAddress, EmailFromName: p.EmailFromName, EmailReplyTo: p.EmailReplyTo, SMTPHost: p.SMTPHost, SMTPPort: p.SMTPPort, SMTPUsername: p.SMTPUsername, SMTPSecurity: p.SMTPSecurity, WorkVisibility: p.WorkVisibility, ShowActivityPresence: p.ShowActivityPresence, ClaimRequired: p.ClaimRequired, ClaimExpiryHours: p.ClaimExpiryHours, RateVisibility: p.RateVisibility, MarginVisibility: p.MarginVisibility, ContactVisibility: p.ContactVisibility, StaffRecordScope: p.StaffRecordScope, ManagerRecordScope: p.ManagerRecordScope, UnownedRecordsVisible: p.UnownedRecordsVisible, InviteExpiryHours: p.InviteExpiryHours, DefaultInviteRole: p.DefaultInviteRole, DefaultBurdenPct: p.burdenUpdate(), AEDTNoticeRequired: p.AEDTNoticeRequired, } } // burdenUpdate turns the wire shape into the three state update the store expects. A malformed number is // deliberately NOT handled here: it becomes a nil inner pointer, which would read as a clear. parseBurden // below is called first by the handler so a bad string is refused with a field name rather than silently // erasing the wrap. func (p patchRequest) burdenUpdate() **decimal.Decimal { if !p.defaultBurdenPresent { return nil } var inner *decimal.Decimal if p.DefaultBurdenPct != nil && strings.TrimSpace(*p.DefaultBurdenPct) != "" { if d, err := decimal.NewFromString(strings.TrimSpace(*p.DefaultBurdenPct)); err == nil { inner = &d } } return &inner } // parseBurden reports whether the supplied wrap is a number at all, so the handler can refuse a typo with the // offending field named rather than treating it as a request to clear the wrap. func (p patchRequest) parseBurden() error { if !p.defaultBurdenPresent || p.DefaultBurdenPct == nil { return nil } v := strings.TrimSpace(*p.DefaultBurdenPct) if v == "" { return nil } if _, err := decimal.NewFromString(v); err != nil { return fmt.Errorf("that is not a number, a 35 percent wrap is written 0.35") } return nil } func (h *SettingsHandler) patch(w http.ResponseWriter, r *http.Request) { if h.store == nil { writeErr(w, http.StatusServiceUnavailable, "settings are unavailable on this node") return } var in patchRequest if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)).Decode(&in); err != nil { writeErr(w, http.StatusBadRequest, "invalid body") return } // A MALFORMED WRAP IS REFUSED BEFORE IT CAN READ AS A CLEAR. burdenUpdate turns an unparseable string into // a nil inner pointer, which the store treats as "erase the wrap", so a typo would silently remove the // organization's cost basis and turn every margin in the product back into a spread. Checking first means // the owner gets the field name and the wrap survives. if err := in.parseBurden(); err != nil { writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": err.Error(), "field": "default_burden_pct"}) return } u := in.toUpdate() if field, err := u.Validate(); err != nil { writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": err.Error(), "field": field}) return } actor := actorUUID(r.Context()) var after orgsettings.Settings err := h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { before, e := h.store.LoadTx(ctx, tx) if e != nil { return e } after, e = h.store.Save(ctx, tx, u, actor) if e != nil { return e } // AUDITED WITH BEFORE AND AFTER, because these settings decide who can see pay data and whose activity // is visible to whom. A change to rate_visibility is a permission change, and permission changes are // exactly what the audit rule names. if h.aud != nil { b, _ := json.Marshal(before) a, _ := json.Marshal(after) return h.aud.OnTx(ctx, tx, audit.Event{ Action: "org.settings.updated", ActorUserID: &actor, EntityType: "org_settings", OldValues: b, NewValues: a, IPAddress: r.Header.Get("X-Forwarded-For"), UserAgent: r.UserAgent(), }) } return nil }) if err != nil { writeErr(w, http.StatusInternalServerError, "could not save settings") return } writeJSON(w, http.StatusOK, map[string]any{"settings": after}) } func (h *SettingsHandler) putPassword(w http.ResponseWriter, r *http.Request) { if h.store == nil { writeErr(w, http.StatusServiceUnavailable, "settings are unavailable on this node") return } if !h.store.SealAvailable() { // A HONEST 503. The alternative, storing it unprotected or dropping it silently, are both worse than // telling the administrator that this node cannot hold a secret yet. writeErr(w, http.StatusServiceUnavailable, "this node cannot store a secret because no organization secret key is configured, contact your operator") return } var in struct { Password string `json:"password"` } if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<10)).Decode(&in); err != nil { writeErr(w, http.StatusBadRequest, "invalid body") return } if strings.TrimSpace(in.Password) == "" { writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "a password is [credential removed], use DELETE to remove the stored one", "field": "password"}) return } actor := actorUUID(r.Context()) err := h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { if e := h.store.PutSecret(ctx, tx, orgsettings.SecretSMTPPassword, in.Password, actor); e != nil { return e } // Storing a new password invalidates any previous proof that mail worked, for the same reason changing // the host does. if e := h.store.MarkAttemptFailed(ctx, tx, "the mail password changed, send a test message to verify it", "", ""); e != nil { return e } if h.aud != nil { // NAME ONLY. The audit trail records that a mail credential was set, never any part of it. body, _ := json.Marshal(map[string]any{"secret": orgsettings.SecretSMTPPassword}) return h.aud.OnTx(ctx, tx, audit.Event{ Action: "org.secret.set", ActorUserID: &actor, EntityType: "org_secret", EntityID: orgsettings.SecretSMTPPassword, NewValues: body, IPAddress: r.Header.Get("X-Forwarded-For"), UserAgent: r.UserAgent(), }) } return nil }) if err != nil { if errors.Is(err, orgsettings.ErrSealUnavailable) { writeErr(w, http.StatusServiceUnavailable, "secret storage is not configured on this node") return } writeErr(w, http.StatusInternalServerError, "could not store the mail password") return } writeJSON(w, http.StatusOK, map[string]any{ "stored": true, "note": "the password is [credential removed] at rest and is never returned, send a test message to verify it works", }) } func (h *SettingsHandler) deletePassword(w http.ResponseWriter, r *http.Request) { if h.store == nil { writeErr(w, http.StatusServiceUnavailable, "settings are unavailable on this node") return } actor := actorUUID(r.Context()) err := h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { if e := h.store.DeleteSecret(ctx, tx, orgsettings.SecretSMTPPassword); e != nil { return e } // Removing the credential stops delivery. Leaving delivery enabled against a credential that no longer // exists would queue invites into a failure nobody is watching. if e := h.store.MarkAttemptFailed(ctx, tx, "the mail password was removed", "", ""); e != nil { return e } if h.aud != nil { body, _ := json.Marshal(map[string]any{"secret": orgsettings.SecretSMTPPassword}) return h.aud.OnTx(ctx, tx, audit.Event{ Action: "org.secret.removed", ActorUserID: &actor, EntityType: "org_secret", EntityID: orgsettings.SecretSMTPPassword, OldValues: body, IPAddress: r.Header.Get("X-Forwarded-For"), UserAgent: r.UserAgent(), }) } return nil }) if err != nil { writeErr(w, http.StatusInternalServerError, "could not remove the mail password") return } writeJSON(w, http.StatusOK, map[string]any{"removed": true}) } // testEmail is the ONLY way smtp_verified_at is ever set, and it sends to the administrator's OWN address, // never to one supplied in the request. That is deliberate: a test endpoint that accepts a recipient is an // open relay wearing a settings screen, letting any authenticated tenant admin send mail from this platform // to anybody they choose. func (h *SettingsHandler) testEmail(w http.ResponseWriter, r *http.Request) { if h.store == nil { writeErr(w, http.StatusServiceUnavailable, "settings are unavailable on this node") return } actor := actorUUID(r.Context()) var cfg mailer.Config var recipient string err := h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { st, e := h.store.LoadTx(ctx, tx) if e != nil { return e } if st.SMTPHost == "" || st.SMTPPort == 0 || st.EmailFromAddress == "" { return errNotConfigured } pw, e := h.store.OpenSecret(ctx, tx, orgsettings.SecretSMTPPassword) if e != nil && !errors.Is(e, orgsettings.ErrNoSecret) { return e } if st.SMTPUsername != "" && errors.Is(e, orgsettings.ErrNoSecret) { return errNoPassword } cfg = mailer.Config{ Host: st.SMTPHost, Port: st.SMTPPort, Username: st.SMTPUsername, Password: pw, Security: st.SMTPSecurity, From: st.EmailFromAddress, FromName: st.EmailFromName, ReplyTo: st.EmailReplyTo, } return tx.QueryRow(ctx, `SELECT email FROM users WHERE id = $1`, actor).Scan(&recipient) }) if err != nil { switch { case errors.Is(err, errNotConfigured): writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "configure a mail host, port and sender address first", "field": "smtp_host"}) case errors.Is(err, errNoPassword): writeJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "a username is set but no password is [credential removed]", "field": "password"}) default: writeErr(w, http.StatusInternalServerError, "could not read the mail configuration") } return } ctx, cancel := context.WithTimeout(r.Context(), 45*time.Second) defer cancel() sendErr := mailer.Send(ctx, cfg, mailer.Message{ To: recipient, Subject: "missionMATCH test message", Body: "This is a test from missionMATCH.\r\n\r\n" + "Your organization's mail settings are working. Invitations and notifications will now be sent " + "from " + cfg.From + " using your own mail server.\r\n\r\n" + "If you did not request this, someone with administrator access to your missionMATCH account " + "changed the mail settings.\r\n", }) // The outcome is recorded either way, in its own transaction, so a failed send still leaves the // administrator an error they can act on rather than a screen that simply did nothing. _ = h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { if sendErr != nil { return h.store.MarkAttemptFailed(ctx, tx, sendErr.Error(), cfg.Username, cfg.Password) } return h.store.MarkVerified(ctx, tx) }) if h.aud != nil { outcome := "sent" if sendErr != nil { outcome = "failed" } body, _ := json.Marshal(map[string]any{"outcome": outcome, "host": cfg.Host}) _ = h.db.RunInTenantTx(r.Context(), func(ctx context.Context, tx pgx.Tx) error { return h.aud.OnTx(ctx, tx, audit.Event{ Action: "org.email.test", ActorUserID: &actor, EntityType: "org_settings", NewValues: body, IPAddress: r.Header.Get("X-Forwarded-For"), UserAgent: r.UserAgent(), }) }) } if sendErr != nil { // The transport's own words, scrubbed of the credentials, because "could not send mail" sends an // administrator to a support queue while "no such host" sends them to their typo. writeJSON(w, http.StatusBadGateway, map[string]string{ "error": redactFor(sendErr.Error(), cfg.Username, cfg.Password), }) return } writeJSON(w, http.StatusOK, map[string]any{ "sent": true, "to": recipient, "note": "mail delivery is now verified, switch on email_delivery_enabled to send invitations this way", }) } var ( errNotConfigured = errors.New("mail is not configured") errNoPassword = [credential removed]"no mail password stored") ) // redactFor mirrors the scrubbing applied before the message is stored, so the value returned over the API // and the value written to the column are the same scrubbed text. func redactFor(s, username, password string) string { for _, secret := range []string{password, username} { if len(secret) >= 4 { s = strings.ReplaceAll(s, secret, "[redacted]") } } return s } func actorUUID(ctx context.Context) uuid.UUID { p, ok := tenant.UserFromContext(ctx) if !ok { return uuid.Nil } id, err := uuid.Parse(p.UserID) if err != nil { return uuid.Nil } return id }

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