mirror of
https://github.com/penpot/penpot.git
synced 2026-08-05 04:18:41 +00:00
🐛 Restrict update-profile-props to documented keys only (#10992)
Close the profile props schema to reject undocumented keys and add a
denylist for system-managed props like :subscription that should not be
user-writable via RPC.
Changes:
- Add system-managed-props denylist (#{:subscription})
- Close schema:props with :closed true
- Add tests for subscription rejection and valid key acceptance
AI-assisted-by: qwen3.7-plus
This commit is contained in:
parent
49119e0339
commit
1136e5eda5
@ -45,8 +45,12 @@
|
||||
[:email-comments [::sm/one-of #{:all :partial :none}]]
|
||||
[:email-invites [::sm/one-of #{:all :none}]]])
|
||||
|
||||
(def system-managed-props
|
||||
"Props keys managed by the system (not user-writable via RPC)."
|
||||
#{:subscription})
|
||||
|
||||
(def schema:props
|
||||
[:map {:title "ProfileProps"}
|
||||
[:map {:title "ProfileProps" :closed true}
|
||||
[:plugins {:optional true} schema:plugin-registry]
|
||||
[:renderer {:optional true} [::sm/one-of #{:svg :wasm}]]
|
||||
[:mcp-enabled {:optional true} ::sm/boolean]
|
||||
@ -454,7 +458,7 @@
|
||||
(assoc props k v))
|
||||
props))
|
||||
(:props profile)
|
||||
props)]
|
||||
(apply dissoc props system-managed-props))]
|
||||
|
||||
(db/update! conn :profile
|
||||
{:props (db/tjson props)}
|
||||
|
||||
@ -1160,3 +1160,45 @@
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :email-as-password))))
|
||||
|
||||
|
||||
(t/deftest update-profile-props-rejects-subscription
|
||||
;; N1-16: Mass Assignment — :subscription must not be writable via RPC
|
||||
;; The closed schema rejects :subscription at validation time
|
||||
(let [profile (th/create-profile* 1)
|
||||
data {::th/type :update-profile-props
|
||||
::rpc/profile-id (:id profile)
|
||||
:props {:subscription {:type "unlimited" :status "active"}}}
|
||||
out (th/command! data)]
|
||||
|
||||
;; The call must fail with validation error
|
||||
(t/is (th/ex-info? (:error out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :params-validation))
|
||||
|
||||
;; And :subscription must NOT be persisted
|
||||
(let [saved (th/db-get :profile {:id (:id profile)})
|
||||
props (profile/decode-row saved)]
|
||||
(t/is (nil? (get-in props [:props :subscription]))
|
||||
":subscription must not be writable via update-profile-props"))))
|
||||
|
||||
|
||||
(t/deftest update-profile-props-accepts-valid-keys
|
||||
;; Verify that valid props keys still work after closing the schema
|
||||
(let [profile (th/create-profile* 1)
|
||||
data {::th/type :update-profile-props
|
||||
::rpc/profile-id (:id profile)
|
||||
:props {:onboarding-viewed true
|
||||
:newsletter-updates false
|
||||
:renderer :wasm}}
|
||||
out (th/command! data)]
|
||||
|
||||
;; The call should succeed
|
||||
(t/is (nil? (:error out)))
|
||||
|
||||
;; And all valid keys should be persisted
|
||||
(let [saved (th/db-get :profile {:id (:id profile)})
|
||||
props (profile/decode-row saved)]
|
||||
(t/is (true? (get-in props [:props :onboarding-viewed])))
|
||||
(t/is (false? (get-in props [:props :newsletter-updates])))
|
||||
(t/is (= :wasm (get-in props [:props :renderer]))))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user