package setting import ( "context" "haixun-backend/internal/library/authctx" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" "haixun-backend/internal/svc" ) // authorizeSettingAccess guards the generic settings API against horizontal // privilege escalation (IDOR). The settings collection stores sensitive values // such as AI / Brave / Exa API keys under the "user" and "account" scopes, so // the caller must be the owner of the scope being accessed. // // - "user": scope_id must equal the caller uid (uid is enumerable). // - "account": the threads account must belong to the caller. // - other scopes (brand / persona / placement_topic / copy_mission ...): // keyed by unguessable ObjectIDs and hold non-secret research config; // ownership is enforced by their own feature endpoints. We still require an // authenticated actor here. func authorizeSettingAccess(ctx context.Context, svcCtx *svc.ServiceContext, scope, scopeID string) error { actor, ok := authctx.ActorFromContext(ctx) if !ok || actor.UID == "" { return app.For(code.Auth).AuthUnauthorized("missing actor") } switch scope { case "user": if scopeID != actor.UID { return app.For(code.Setting).AuthForbidden("cannot access another user's settings") } case "account": if _, err := svcCtx.ThreadsAccount.Get(ctx, actor.TenantID, actor.UID, scopeID); err != nil { return app.For(code.Setting).AuthForbidden("cannot access settings of an account you do not own") } } return nil }