mirror of
https://github.com/penpot/penpot.git
synced 2025-12-31 02:18:40 -05:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de05521b57 | ||
|
|
c86afca1d0 | ||
|
|
5617ca24b8 | ||
|
|
cd51f2f652 | ||
|
|
00bb988ecc | ||
|
|
5efc56eb5a | ||
|
|
0ccae600bc | ||
|
|
b0af94415f | ||
|
|
380ead2ad6 | ||
|
|
3df45d697d | ||
|
|
efb70f0b97 | ||
|
|
924c1d60f9 | ||
|
|
6b80f19e5f | ||
|
|
000d2c3935 | ||
|
|
91435bf372 | ||
|
|
ea5c22c244 | ||
|
|
e07c1bba7a | ||
|
|
ec56a4149b | ||
|
|
314742a563 | ||
|
|
38c9e3e7cc | ||
|
|
2533d0ebc0 |
@@ -4,6 +4,12 @@
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Don't allow registry with email and password, if password login is disabled (invitation workflow) [Github #4975](https://github.com/penpot/penpot/issues/4975)
|
||||
|
||||
## 2.1.2
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- User switch language to "zh_hant" will get 400 [Github #4884](https://github.com/penpot/penpot/issues/4884)
|
||||
- Smtp config ignoring port if ssl is set [Github #4872](https://github.com/penpot/penpot/issues/4872)
|
||||
- Ability to let users to authenticate with a private oidc provider only [Github #4963](https://github.com/penpot/penpot/issues/4963)
|
||||
|
||||
@@ -407,6 +407,7 @@
|
||||
(ex/raise :type :not-found
|
||||
:code :object-not-found
|
||||
:table table
|
||||
:params params
|
||||
:hint "database object not found"))
|
||||
row))
|
||||
|
||||
|
||||
@@ -58,28 +58,26 @@
|
||||
(defn load-pointer
|
||||
"A database loader pointer helper"
|
||||
[system file-id id]
|
||||
(let [{:keys [content]} (db/get system :file-data-fragment
|
||||
{:id id :file-id file-id}
|
||||
{::sql/columns [:content]
|
||||
::db/check-deleted false})]
|
||||
(let [fragment (db/get* system :file-data-fragment
|
||||
{:id id :file-id file-id}
|
||||
{::sql/columns [:data]})]
|
||||
|
||||
(l/trc :hint "load pointer"
|
||||
:file-id (str file-id)
|
||||
:id (str id)
|
||||
:found (some? content))
|
||||
:found (some? fragment))
|
||||
|
||||
(when-not content
|
||||
(when-not fragment
|
||||
(ex/raise :type :internal
|
||||
:code :fragment-not-found
|
||||
:hint "fragment not found"
|
||||
:file-id file-id
|
||||
:fragment-id id))
|
||||
|
||||
(blob/decode content)))
|
||||
(blob/decode (:data fragment))))
|
||||
|
||||
(defn persist-pointers!
|
||||
"Given a database connection and the final file-id, persist all
|
||||
pointers to the underlying storage (the database)."
|
||||
"Persist all currently tracked pointer objects"
|
||||
[system file-id]
|
||||
(let [conn (db/get-connection system)]
|
||||
(doseq [[id item] @pmap/*tracked*]
|
||||
@@ -89,7 +87,7 @@
|
||||
(db/insert! conn :file-data-fragment
|
||||
{:id id
|
||||
:file-id file-id
|
||||
:content content}))))))
|
||||
:data content}))))))
|
||||
|
||||
(defn process-pointers
|
||||
"Apply a function to all pointers on the file. Usuly used for
|
||||
|
||||
@@ -449,7 +449,9 @@
|
||||
{::db/pool (ig/ref ::db/pool)
|
||||
::sto/backends
|
||||
{:assets-s3 (ig/ref [::assets :app.storage.s3/backend])
|
||||
:assets-fs (ig/ref [::assets :app.storage.fs/backend])}}
|
||||
:assets-fs (ig/ref [::assets :app.storage.fs/backend])
|
||||
:s3 (ig/ref [::assets :app.storage.s3/backend])
|
||||
:fs (ig/ref [::assets :app.storage.fs/backend])}}
|
||||
|
||||
[::assets :app.storage.s3/backend]
|
||||
{::sto.s3/region (cf/get :storage-assets-s3-region)
|
||||
|
||||
@@ -379,7 +379,10 @@
|
||||
:fn (mg/resource "app/migrations/sql/0119-mod-file-table.sql")}
|
||||
|
||||
{:name "0120-mod-audit-log-table"
|
||||
:fn (mg/resource "app/migrations/sql/0120-mod-audit-log-table.sql")}])
|
||||
:fn (mg/resource "app/migrations/sql/0120-mod-audit-log-table.sql")}
|
||||
|
||||
{:name "0121-mod-file-data-fragment-table"
|
||||
:fn (mg/resource "app/migrations/sql/0121-mod-file-data-fragment-table.sql")}])
|
||||
|
||||
(defn apply-migrations!
|
||||
[pool name migrations]
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
ALTER TABLE file_data_fragment
|
||||
ADD COLUMN data bytea NULL;
|
||||
|
||||
UPDATE file_data_fragment
|
||||
SET data = content;
|
||||
|
||||
ALTER TABLE file_data_fragment
|
||||
DROP COLUMN content;
|
||||
@@ -180,10 +180,11 @@
|
||||
(defn- validate-register-attempt!
|
||||
[cfg params]
|
||||
|
||||
(when-not (contains? cf/flags :registration)
|
||||
(when-not (contains? params :invitation-token)
|
||||
(ex/raise :type :restriction
|
||||
:code :registration-disabled)))
|
||||
(when (or
|
||||
(not (contains? cf/flags :registration))
|
||||
(not (contains? cf/flags :login-with-password)))
|
||||
(ex/raise :type :restriction
|
||||
:code :registration-disabled))
|
||||
|
||||
(when (contains? params :invitation-token)
|
||||
(let [invitation (tokens/verify (::setup/props cfg)
|
||||
@@ -282,6 +283,7 @@
|
||||
is-demo (:is-demo params false)
|
||||
is-muted (:is-muted params false)
|
||||
is-active (:is-active params false)
|
||||
theme (:theme params nil)
|
||||
email (str/lower email)
|
||||
|
||||
params {:id id
|
||||
@@ -292,6 +294,7 @@
|
||||
:password password
|
||||
:deleted-at (:deleted-at params)
|
||||
:props props
|
||||
:theme theme
|
||||
:is-active is-active
|
||||
:is-muted is-muted
|
||||
:is-demo is-demo}]
|
||||
@@ -347,24 +350,32 @@
|
||||
:extra-data ptoken})))
|
||||
|
||||
(defn register-profile
|
||||
[{:keys [::db/conn] :as cfg} {:keys [token fullname] :as params}]
|
||||
(let [claims (tokens/verify (::setup/props cfg) {:token token :iss :prepared-register})
|
||||
[{:keys [::db/conn] :as cfg} {:keys [token fullname theme] :as params}]
|
||||
(let [theme (when (= theme "light") theme)
|
||||
claims (tokens/verify (::setup/props cfg) {:token token :iss :prepared-register})
|
||||
params (-> claims
|
||||
(into params)
|
||||
(assoc :fullname fullname))
|
||||
(assoc :fullname fullname)
|
||||
(assoc :theme theme))
|
||||
|
||||
profile (if-let [profile-id (:profile-id claims)]
|
||||
(profile/get-profile conn profile-id)
|
||||
(let [is-active (or (boolean (:is-active claims))
|
||||
(not (contains? cf/flags :email-verification)))
|
||||
params (-> params
|
||||
(assoc :is-active is-active)
|
||||
(update :password #(profile/derive-password cfg %)))]
|
||||
(->> (create-profile! conn params)
|
||||
(create-profile-rels! conn))))
|
||||
;; NOTE: we first try to match existing profile
|
||||
;; by email, that in normal circumstances will
|
||||
;; not return anything, but when a user tries to
|
||||
;; reuse the same token multiple times, we need
|
||||
;; to detect if the profile is already registered
|
||||
(or (profile/get-profile-by-email conn (:email claims))
|
||||
(let [is-active (or (boolean (:is-active claims))
|
||||
(not (contains? cf/flags :email-verification)))
|
||||
params (-> params
|
||||
(assoc :is-active is-active)
|
||||
(update :password #(profile/derive-password cfg %)))
|
||||
profile (->> (create-profile! conn params)
|
||||
(create-profile-rels! conn))]
|
||||
(vary-meta profile assoc :created true))))
|
||||
|
||||
;; When no profile-id comes on claims means a new register
|
||||
created? (not (:profile-id claims))
|
||||
created? (-> profile meta :created true?)
|
||||
|
||||
invitation (when-let [token (:invitation-token params)]
|
||||
(tokens/verify (::setup/props cfg) {:token token :iss :team-invitation}))
|
||||
@@ -422,13 +433,13 @@
|
||||
::audit/profile-id (:id profile)})))
|
||||
|
||||
:else
|
||||
(let [elapsed? (elapsed-verify-threshold? profile)
|
||||
complaints? (eml/has-reports? conn (:email profile))
|
||||
action (if complaints?
|
||||
"ignore-because-complaints"
|
||||
(if elapsed?
|
||||
"resend-email-verification"
|
||||
"ignore"))]
|
||||
(let [elapsed? (elapsed-verify-threshold? profile)
|
||||
reports? (eml/has-reports? conn (:email profile))
|
||||
action (if reports?
|
||||
"ignore-because-complaints"
|
||||
(if elapsed?
|
||||
"resend-email-verification"
|
||||
"ignore"))]
|
||||
|
||||
(l/wrn :hint "repeated registry detected"
|
||||
:profile-id (str (:id profile))
|
||||
@@ -450,7 +461,8 @@
|
||||
(def schema:register-profile
|
||||
[:map {:title "register-profile"}
|
||||
[:token schema:token]
|
||||
[:fullname [::sm/word-string {:max 100}]]])
|
||||
[:fullname [::sm/word-string {:max 100}]]
|
||||
[:theme {:optional true} [:string {:max 10}]]])
|
||||
|
||||
(sv/defmethod ::register-profile
|
||||
{::rpc/auth false
|
||||
|
||||
@@ -341,9 +341,9 @@
|
||||
[:share-id {:optional true} ::sm/uuid]])
|
||||
|
||||
(defn- get-file-fragment
|
||||
[conn file-id fragment-id]
|
||||
(some-> (db/get conn :file-data-fragment {:file-id file-id :id fragment-id})
|
||||
(update :content blob/decode)))
|
||||
[cfg file-id fragment-id]
|
||||
(some-> (db/get cfg :file-data-fragment {:file-id file-id :id fragment-id})
|
||||
(update :data blob/decode)))
|
||||
|
||||
(sv/defmethod ::get-file-fragment
|
||||
"Retrieve a file fragment by its ID. Only authenticated users."
|
||||
@@ -351,12 +351,12 @@
|
||||
::rpc/auth false
|
||||
::sm/params schema:get-file-fragment
|
||||
::sm/result schema:file-fragment}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id fragment-id share-id]}]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(let [perms (get-permissions conn profile-id file-id share-id)]
|
||||
(check-read-permissions! perms)
|
||||
(-> (get-file-fragment conn file-id fragment-id)
|
||||
(rph/with-http-cache long-cache-duration)))))
|
||||
[cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}]
|
||||
(db/run! cfg (fn [cfg]
|
||||
(let [perms (get-permissions cfg profile-id file-id share-id)]
|
||||
(check-read-permissions! perms)
|
||||
(-> (get-file-fragment cfg file-id fragment-id)
|
||||
(rph/with-http-cache long-cache-duration))))))
|
||||
|
||||
;; --- COMMAND QUERY: get-project-files
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.db.sql :as-alias sql]
|
||||
[app.http.session :as session]
|
||||
@@ -152,11 +153,12 @@
|
||||
|
||||
(us/verify! ::team-invitation-claims claims)
|
||||
|
||||
(let [invitation (db/get* conn :team-invitation
|
||||
{:team-id team-id :email-to member-email})
|
||||
profile (db/get* conn :profile
|
||||
{:id profile-id}
|
||||
{:columns [:id :email]})]
|
||||
(let [invitation (db/get* conn :team-invitation
|
||||
{:team-id team-id :email-to member-email})
|
||||
profile (db/get* conn :profile
|
||||
{:id profile-id}
|
||||
{:columns [:id :email]})
|
||||
registration-disabled? (not (contains? cf/flags :registration))]
|
||||
(when (nil? invitation)
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-token
|
||||
@@ -185,12 +187,12 @@
|
||||
:hint "logged-in user does not matches the invitation"))
|
||||
|
||||
;; If we have not logged-in user, and invitation comes with member-id we
|
||||
;; redirect user to login, if no memeber-id is present in the invitation
|
||||
;; token, we redirect user the the register page.
|
||||
;; redirect user to login, if no memeber-id is present and in the invitation
|
||||
;; token and registration is enabled, we redirect user the the register page.
|
||||
|
||||
{:invitation-token token
|
||||
:iss :team-invitation
|
||||
:redirect-to (if member-id :auth-login :auth-register)
|
||||
:redirect-to (if (or member-id registration-disabled?) :auth-login :auth-register)
|
||||
:state :pending})))
|
||||
|
||||
;; --- Default
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
;; Storage Module State
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::id #{:assets-fs :assets-s3})
|
||||
(s/def ::id #{:assets-fs :assets-s3 :s3 :fs})
|
||||
(s/def ::s3 ::ss3/backend)
|
||||
(s/def ::fs ::sfs/backend)
|
||||
(s/def ::type #{:fs :s3})
|
||||
|
||||
@@ -505,6 +505,54 @@
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (= 0 (:call-count @mock))))))))
|
||||
|
||||
(t/deftest prepare-and-register-with-invitation-and-enabled-registration-1
|
||||
(let [sprops (:app.setup/props th/*system*)
|
||||
itoken (tokens/generate sprops
|
||||
{:iss :team-invitation
|
||||
:exp (dt/in-future "48h")
|
||||
:role :editor
|
||||
:team-id uuid/zero
|
||||
:member-email "user@example.com"})
|
||||
data {::th/type :prepare-register-profile
|
||||
:invitation-token itoken
|
||||
:email "user@example.com"
|
||||
:password "foobar"}
|
||||
|
||||
{:keys [result error] :as out} (th/command! data)]
|
||||
(t/is (nil? error))
|
||||
(t/is (map? result))
|
||||
(t/is (string? (:token result)))
|
||||
|
||||
(let [rtoken (:token result)
|
||||
data {::th/type :register-profile
|
||||
:token rtoken
|
||||
:fullname "foobar"}
|
||||
|
||||
{:keys [result error] :as out} (th/command! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? error))
|
||||
(t/is (map? result))
|
||||
(t/is (string? (:invitation-token result))))))
|
||||
|
||||
(t/deftest prepare-and-register-with-invitation-and-enabled-registration-2
|
||||
(let [sprops (:app.setup/props th/*system*)
|
||||
itoken (tokens/generate sprops
|
||||
{:iss :team-invitation
|
||||
:exp (dt/in-future "48h")
|
||||
:role :editor
|
||||
:team-id uuid/zero
|
||||
:member-email "user2@example.com"})
|
||||
|
||||
data {::th/type :prepare-register-profile
|
||||
:invitation-token itoken
|
||||
:email "user@example.com"
|
||||
:password "foobar"}
|
||||
out (th/command! data)]
|
||||
|
||||
(t/is (not (th/success? out)))
|
||||
(let [edata (-> out :error ex-data)]
|
||||
(t/is (= :restriction (:type edata)))
|
||||
(t/is (= :email-does-not-match-invitation (:code edata))))))
|
||||
|
||||
(t/deftest prepare-and-register-with-invitation-and-disabled-registration-1
|
||||
(with-redefs [app.config/flags [:disable-registration]]
|
||||
@@ -519,22 +567,12 @@
|
||||
:invitation-token itoken
|
||||
:email "user@example.com"
|
||||
:password "foobar"}
|
||||
out (th/command! data)]
|
||||
|
||||
{:keys [result error] :as out} (th/command! data)]
|
||||
(t/is (nil? error))
|
||||
(t/is (map? result))
|
||||
(t/is (string? (:token result)))
|
||||
|
||||
(let [rtoken (:token result)
|
||||
data {::th/type :register-profile
|
||||
:token rtoken
|
||||
:fullname "foobar"}
|
||||
|
||||
{:keys [result error] :as out} (th/command! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? error))
|
||||
(t/is (map? result))
|
||||
(t/is (string? (:invitation-token result)))))))
|
||||
(t/is (not (th/success? out)))
|
||||
(let [edata (-> out :error ex-data)]
|
||||
(t/is (= :restriction (:type edata)))
|
||||
(t/is (= :registration-disabled (:code edata)))))))
|
||||
|
||||
(t/deftest prepare-and-register-with-invitation-and-disabled-registration-2
|
||||
(with-redefs [app.config/flags [:disable-registration]]
|
||||
@@ -555,7 +593,28 @@
|
||||
(t/is (not (th/success? out)))
|
||||
(let [edata (-> out :error ex-data)]
|
||||
(t/is (= :restriction (:type edata)))
|
||||
(t/is (= :email-does-not-match-invitation (:code edata)))))))
|
||||
(t/is (= :registration-disabled (:code edata)))))))
|
||||
|
||||
(t/deftest prepare-and-register-with-invitation-and-disabled-login-with-password
|
||||
(with-redefs [app.config/flags [:disable-login-with-password]]
|
||||
(let [sprops (:app.setup/props th/*system*)
|
||||
itoken (tokens/generate sprops
|
||||
{:iss :team-invitation
|
||||
:exp (dt/in-future "48h")
|
||||
:role :editor
|
||||
:team-id uuid/zero
|
||||
:member-email "user2@example.com"})
|
||||
|
||||
data {::th/type :prepare-register-profile
|
||||
:invitation-token itoken
|
||||
:email "user@example.com"
|
||||
:password "foobar"}
|
||||
out (th/command! data)]
|
||||
|
||||
(t/is (not (th/success? out)))
|
||||
(let [edata (-> out :error ex-data)]
|
||||
(t/is (= :restriction (:type edata)))
|
||||
(t/is (= :registration-disabled (:code edata)))))))
|
||||
|
||||
(t/deftest prepare-register-with-registration-disabled
|
||||
(with-redefs [app.config/flags #{}]
|
||||
|
||||
@@ -125,3 +125,7 @@ services:
|
||||
ports:
|
||||
- "10389:10389"
|
||||
- "10636:10636"
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: "1024"
|
||||
hard: "1024"
|
||||
@@ -38,7 +38,10 @@ http {
|
||||
|
||||
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json;
|
||||
|
||||
resolver $PENPOT_INTERNAL_RESOLVER;
|
||||
proxy_buffer_size 16k;
|
||||
proxy_busy_buffers_size 24k; # essentially, proxy_buffer_size + 2 small buffers of 4k
|
||||
proxy_buffers 32 4k;
|
||||
resolver $PENPOT_INTERNAL_RESOLVER ipv6=off;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"~:id": "~u015fda4f-caa6-8103-8004-862a9e4b4d4b",
|
||||
"~:file-id": "~u015fda4f-caa6-8103-8004-862a00dd4f31",
|
||||
"~:created-at": "~m1718718436639",
|
||||
"~:content": {
|
||||
"~:data": {
|
||||
"~ue117f7f6-433c-807e-8004-862a38e1823d": {
|
||||
"~:id": "~ue117f7f6-433c-807e-8004-862a38e1823d",
|
||||
"~:name": "Button",
|
||||
@@ -28,4 +28,4 @@
|
||||
"~:main-instance-page": "~u015fda4f-caa6-8103-8004-862a00ddbe94"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"~:id": "~u015fda4f-caa6-8103-8004-862a9e4ad279",
|
||||
"~:file-id": "~u015fda4f-caa6-8103-8004-862a00dd4f31",
|
||||
"~:created-at": "~m1718718436639",
|
||||
"~:content": {
|
||||
"~:data": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
@@ -627,4 +627,4 @@
|
||||
"~:id": "~u015fda4f-caa6-8103-8004-862a00ddbe94",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"~:id": "~u03bff843-920f-81a1-8004-7563acdc8ca1",
|
||||
"~:file-id": "~u03bff843-920f-81a1-8004-756365e1eb6a",
|
||||
"~:created-at": "~m1717592543081",
|
||||
"~:content": {
|
||||
"~:data": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
@@ -360,4 +360,4 @@
|
||||
"~:id": "~u03bff843-920f-81a1-8004-756365e1eb6b",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"~:id": "~u0515a066-e303-8169-8004-73eb58e899c2",
|
||||
"~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:created-at": "~m1717493890966",
|
||||
"~:content": {
|
||||
"~:data": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
@@ -94,4 +94,4 @@
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f28044384a",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"~:id": "~udd5cc0bb-91ff-81b9-8004-77dfae2d9e7c",
|
||||
"~:file-id": "~udd5cc0bb-91ff-81b9-8004-77df9cd3edb1",
|
||||
"~:created-at": "~m1717759268004",
|
||||
"~:content": {
|
||||
"~:data": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
@@ -183,4 +183,4 @@
|
||||
"~:id": "~udd5cc0bb-91ff-81b9-8004-77df9cd3edb2",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"~:id": "~ucd90e028-326a-80b4-8004-7cdeefa23ece",
|
||||
"~:file-id": "~ucd90e028-326a-80b4-8004-7cdec16ffad5",
|
||||
"~:created-at": "~m1718094617214",
|
||||
"~:content": {
|
||||
"~:data": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"~:id": "~ude58c8f6-c5c2-8196-8004-3df9e2e52d88",
|
||||
"~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:created-at": "~m1713873823631",
|
||||
"~:content": {
|
||||
"~:data": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
@@ -94,4 +94,4 @@
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f28044384a",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
[app.main.data.websocket :as ws]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :refer [storage]]
|
||||
@@ -135,7 +136,8 @@
|
||||
(swap! storage assoc :profile profile)
|
||||
(i18n/set-locale! (:lang profile))
|
||||
(when (not= previous-email email)
|
||||
(set-current-team! nil)))))))
|
||||
(set-current-team! nil))
|
||||
(dom/set-html-theme-color (or (:theme profile) "default")))))))
|
||||
|
||||
(defn fetch-profile
|
||||
[]
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
(uuid? share-id)
|
||||
(assoc :share-id share-id))]
|
||||
(->> (rp/cmd! :get-file-fragment params)
|
||||
(rx/map :content)
|
||||
(rx/map :data)
|
||||
(rx/map #(vector key %)))))]
|
||||
|
||||
(->> (rp/cmd! :get-view-only-bundle params')
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
[app.main.data.workspace.collapse :as dwco]
|
||||
[app.main.data.workspace.drawing :as dwd]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.fix-bool-contents :as fbc]
|
||||
[app.main.data.workspace.fix-broken-shapes :as fbs]
|
||||
[app.main.data.workspace.fix-deleted-fonts :as fdf]
|
||||
[app.main.data.workspace.groups :as dwg]
|
||||
@@ -129,7 +128,6 @@
|
||||
(when (and (not (boolean (-> state :profile :props :v2-info-shown)))
|
||||
(features/active-feature? state "components/v2"))
|
||||
(modal/show :v2-info {}))
|
||||
(fbc/fix-bool-contents)
|
||||
(fdf/fix-deleted-fonts)
|
||||
(fbs/fix-broken-shapes)))))
|
||||
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.data.workspace.fix-bool-contents
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; This event will update the file so the boolean data has a pre-generated path data
|
||||
;; to increase performance.
|
||||
;; For new shapes this will be generated in the :reg-objects but we need to do this for
|
||||
;; old files.
|
||||
|
||||
;; FIXME: Remove me after June 2022
|
||||
|
||||
(defn fix-bool-contents
|
||||
"This event will calculate the bool content and update the page. This is kind of a 'addhoc' migration
|
||||
to fill the optional value 'bool-content'"
|
||||
[]
|
||||
|
||||
(letfn [(should-migrate-shape? [shape]
|
||||
(and (= :bool (:type shape)) (not (contains? shape :bool-content))))
|
||||
|
||||
(should-migrate-component? [component]
|
||||
(->> (:objects component)
|
||||
(vals)
|
||||
(d/seek should-migrate-shape?)))
|
||||
|
||||
(update-shape [shape objects]
|
||||
(cond-> shape
|
||||
(should-migrate-shape? shape)
|
||||
(assoc :bool-content (gsh/calc-bool-content shape objects))))
|
||||
|
||||
(migrate-component [component]
|
||||
(-> component
|
||||
(update
|
||||
:objects
|
||||
(fn [objects]
|
||||
(d/mapm #(update-shape %2 objects) objects)))))
|
||||
|
||||
(update-library
|
||||
[library]
|
||||
(-> library
|
||||
(d/update-in-when
|
||||
[:data :components]
|
||||
(fn [components]
|
||||
(d/mapm #(migrate-component %2) components)))))]
|
||||
|
||||
(ptk/reify ::fix-bool-contents
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; Update (only-local) the imported libraries
|
||||
(-> state
|
||||
(d/update-when
|
||||
:workspace-libraries
|
||||
(fn [libraries] (d/mapm #(update-library %2) libraries)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
|
||||
ids (into #{}
|
||||
(comp (filter should-migrate-shape?) (map :id))
|
||||
(vals objects))
|
||||
|
||||
components (->> (wsh/lookup-local-components state)
|
||||
(vals)
|
||||
(filter should-migrate-component?))
|
||||
|
||||
component-changes
|
||||
(into []
|
||||
(map (fn [component]
|
||||
{:type :mod-component
|
||||
:id (:id component)
|
||||
:objects (-> component migrate-component :objects)}))
|
||||
components)]
|
||||
|
||||
(rx/of (dwsh/update-shapes ids #(update-shape % objects) {:reg-objects? false
|
||||
:save-undo? false
|
||||
:ignore-tree true}))
|
||||
|
||||
(if (empty? component-changes)
|
||||
(rx/empty)
|
||||
(rx/of (dch/commit-changes {:origin it
|
||||
:redo-changes component-changes
|
||||
:undo-changes []
|
||||
:save-undo? false}))))))))
|
||||
@@ -6,11 +6,9 @@
|
||||
|
||||
(ns app.main.data.workspace.fix-deleted-fonts
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.text :as txt]
|
||||
[app.main.data.changes :as dwc]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.fonts :as fonts]
|
||||
[beicon.v2.core :as rx]
|
||||
@@ -22,14 +20,7 @@
|
||||
;; - Moving files from one team to another in the same instance
|
||||
;; - Custom fonts are explicitly deleted in the team area
|
||||
|
||||
(defn has-invalid-font-family
|
||||
[node]
|
||||
(let [fonts (deref fonts/fontsdb)]
|
||||
(and
|
||||
(some? (:font-family node))
|
||||
(nil? (get fonts (:font-id node))))))
|
||||
|
||||
(defn calculate-alternative-font-id
|
||||
(defn- calculate-alternative-font-id
|
||||
[value]
|
||||
(let [fonts (deref fonts/fontsdb)]
|
||||
(->> (vals fonts)
|
||||
@@ -37,39 +28,44 @@
|
||||
(first)
|
||||
:id)))
|
||||
|
||||
(defn should-fix-deleted-font-shape?
|
||||
(defn- has-invalid-font-family?
|
||||
[node]
|
||||
(let [fonts (deref fonts/fontsdb)
|
||||
font-family (:font-family node)
|
||||
alternative-font-id (calculate-alternative-font-id font-family)]
|
||||
(and (some? font-family)
|
||||
(nil? (get fonts (:font-id node)))
|
||||
(some? alternative-font-id))))
|
||||
|
||||
(defn- should-fix-deleted-font-shape?
|
||||
[shape]
|
||||
(let [text-nodes (txt/node-seq txt/is-text-node? (:content shape))]
|
||||
(and (cfh/text-shape? shape) (some has-invalid-font-family text-nodes))))
|
||||
(and (cfh/text-shape? shape)
|
||||
(some has-invalid-font-family? text-nodes))))
|
||||
|
||||
(defn should-fix-deleted-font-component?
|
||||
(defn- should-fix-deleted-font-component?
|
||||
[component]
|
||||
(->> (:objects component)
|
||||
(vals)
|
||||
(d/seek should-fix-deleted-font-shape?)))
|
||||
(let [xf (comp (map val)
|
||||
(filter should-fix-deleted-font-shape?))]
|
||||
(first (sequence xf (:objects component)))))
|
||||
|
||||
(defn should-fix-deleted-font-typography?
|
||||
[typography]
|
||||
(let [fonts (deref fonts/fontsdb)]
|
||||
(nil? (get fonts (:font-id typography)))))
|
||||
|
||||
(defn fix-deleted-font
|
||||
(defn- fix-deleted-font
|
||||
[node]
|
||||
(let [alternative-font-id (calculate-alternative-font-id (:font-family node))]
|
||||
(cond-> node
|
||||
(some? alternative-font-id) (assoc :font-id alternative-font-id))))
|
||||
|
||||
(defn fix-deleted-font-shape
|
||||
(defn- fix-deleted-font-shape
|
||||
[shape]
|
||||
(let [transform (partial txt/transform-nodes has-invalid-font-family fix-deleted-font)]
|
||||
(let [transform (partial txt/transform-nodes has-invalid-font-family? fix-deleted-font)]
|
||||
(update shape :content transform)))
|
||||
|
||||
(defn fix-deleted-font-component
|
||||
(defn- fix-deleted-font-component
|
||||
[component]
|
||||
(update component
|
||||
:objects
|
||||
(fn [objects]
|
||||
(d/mapm #(fix-deleted-font-shape %2) objects))))
|
||||
(update-vals objects fix-deleted-font-shape))))
|
||||
|
||||
(defn fix-deleted-font-typography
|
||||
[typography]
|
||||
@@ -77,54 +73,60 @@
|
||||
(cond-> typography
|
||||
(some? alternative-font-id) (assoc :font-id alternative-font-id))))
|
||||
|
||||
(defn- generate-deleted-font-shape-changes
|
||||
[{:keys [objects id]}]
|
||||
(sequence
|
||||
(comp (map val)
|
||||
(filter should-fix-deleted-font-shape?)
|
||||
(map (fn [shape]
|
||||
{:type :mod-obj
|
||||
:id (:id shape)
|
||||
:page-id id
|
||||
:operations [{:type :set
|
||||
:attr :content
|
||||
:val (:content (fix-deleted-font-shape shape))}
|
||||
{:type :set
|
||||
:attr :position-data
|
||||
:val nil}]})))
|
||||
objects))
|
||||
|
||||
(defn- generate-deleted-font-components-changes
|
||||
[state]
|
||||
(sequence
|
||||
(comp (map val)
|
||||
(filter should-fix-deleted-font-component?)
|
||||
(map (fn [component]
|
||||
{:type :mod-component
|
||||
:id (:id component)
|
||||
:objects (-> (fix-deleted-font-component component) :objects)})))
|
||||
(wsh/lookup-local-components state)))
|
||||
|
||||
(defn- generate-deleted-font-typography-changes
|
||||
[state]
|
||||
(sequence
|
||||
(comp (map val)
|
||||
(filter has-invalid-font-family?)
|
||||
(map (fn [typography]
|
||||
{:type :mod-typography
|
||||
:typography (fix-deleted-font-typography typography)})))
|
||||
(get-in state [:workspace-data :typographies])))
|
||||
|
||||
(defn fix-deleted-fonts
|
||||
[]
|
||||
(ptk/reify ::fix-deleted-fonts
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
|
||||
ids (into #{}
|
||||
(comp (filter should-fix-deleted-font-shape?) (map :id))
|
||||
(vals objects))
|
||||
|
||||
components (->> (wsh/lookup-local-components state)
|
||||
(vals)
|
||||
(filter should-fix-deleted-font-component?))
|
||||
|
||||
component-changes
|
||||
(into []
|
||||
(map (fn [component]
|
||||
{:type :mod-component
|
||||
:id (:id component)
|
||||
:objects (-> (fix-deleted-font-component component) :objects)}))
|
||||
components)
|
||||
|
||||
typographies (->> (get-in state [:workspace-data :typographies])
|
||||
(vals)
|
||||
(filter should-fix-deleted-font-typography?))
|
||||
|
||||
typography-changes
|
||||
(into []
|
||||
(map (fn [typography]
|
||||
{:type :mod-typography
|
||||
:typography (fix-deleted-font-typography typography)}))
|
||||
typographies)]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (dwsh/update-shapes ids #(fix-deleted-font-shape %) {:reg-objects? false
|
||||
:save-undo? false
|
||||
:ignore-tree true}))
|
||||
(if (empty? component-changes)
|
||||
(rx/empty)
|
||||
(rx/of (dwc/commit-changes {:origin it
|
||||
:redo-changes component-changes
|
||||
:undo-changes []
|
||||
:save-undo? false})))
|
||||
|
||||
(if (empty? typography-changes)
|
||||
(rx/empty)
|
||||
(rx/of (dwc/commit-changes {:origin it
|
||||
:redo-changes typography-changes
|
||||
:undo-changes []
|
||||
:save-undo? false}))))))))
|
||||
(let [data (get state :workspace-data)
|
||||
shape-changes (mapcat generate-deleted-font-shape-changes (vals (:pages-index data)))
|
||||
components-changes (generate-deleted-font-components-changes state)
|
||||
typography-changes (generate-deleted-font-typography-changes state)
|
||||
changes (concat shape-changes
|
||||
components-changes
|
||||
typography-changes)]
|
||||
(if (seq changes)
|
||||
(rx/of (dwc/commit-changes
|
||||
{:origin it
|
||||
:redo-changes (vec changes)
|
||||
:undo-changes []
|
||||
:save-undo? false}))
|
||||
(rx/empty))))))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
(letfn [(resolve-pointer [[key val :as kv]]
|
||||
(if (t/pointer? val)
|
||||
(->> (rp/cmd! :get-file-fragment {:file-id id :fragment-id @val})
|
||||
(rx/map #(get % :content))
|
||||
(rx/map #(get % :data))
|
||||
(rx/map #(vector key %)))
|
||||
(rx/of kv)))
|
||||
|
||||
|
||||
@@ -49,26 +49,27 @@
|
||||
(not= section :auth-register-success))
|
||||
params (:query-params route)
|
||||
error (:error params)
|
||||
hide-image-auth? (cf/external-feature-flag "signup-01" "test")]
|
||||
default-light? (cf/external-feature-flag "onboarding-02" "test")]
|
||||
|
||||
(mf/with-effect []
|
||||
(dom/set-html-title (tr "title.default")))
|
||||
|
||||
(mf/with-effect [default-light?]
|
||||
(when default-light?
|
||||
(dom/set-html-theme-color "light")))
|
||||
|
||||
(mf/with-effect [error]
|
||||
(when error
|
||||
(st/emit! (du/show-redirect-error error))))
|
||||
|
||||
[:main {:class (stl/css-case :auth-section (not hide-image-auth?)
|
||||
:auth-section-hide-image hide-image-auth?)}
|
||||
[:main {:class (stl/css :auth-section)}
|
||||
(when show-login-icon
|
||||
[:h1 {:class (stl/css :logo-container)}
|
||||
[:a {:href "#/" :title "Penpot" :class (stl/css :logo-btn)} i/logo]])
|
||||
(when (not hide-image-auth?)
|
||||
[:div {:class (stl/css :login-illustration)}
|
||||
i/login-illustration])
|
||||
[:div {:class (stl/css :login-illustration)}
|
||||
i/login-illustration]
|
||||
|
||||
[:section {:class (stl/css-case :auth-content (not hide-image-auth?)
|
||||
:auth-content-hide-image hide-image-auth?)}
|
||||
[:section {:class (stl/css :auth-content)}
|
||||
|
||||
(case section
|
||||
:auth-register
|
||||
|
||||
@@ -24,24 +24,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// A-B text signup-01
|
||||
.auth-section-hide-image {
|
||||
position: relative;
|
||||
align-items: center;
|
||||
background: var(--panel-background-color);
|
||||
display: grid;
|
||||
gap: $s-32;
|
||||
height: 100%;
|
||||
padding: $s-32;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
|
||||
@media (max-width: 992px) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
position: absolute;
|
||||
top: $s-20;
|
||||
@@ -83,19 +65,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// A-B text signup-01
|
||||
.auth-content-hide-image {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
gap: $s-24;
|
||||
height: fit-content;
|
||||
margin: auto;
|
||||
max-width: $s-412;
|
||||
padding-block-end: $s-8;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo-btn {
|
||||
svg {
|
||||
width: $s-120;
|
||||
|
||||
@@ -55,14 +55,15 @@
|
||||
(.replace js/location redirect-uri)
|
||||
(log/error :hint "unexpected response from OIDC method"
|
||||
:resp (pr-str rsp))))
|
||||
(fn [{:keys [type code] :as error}]
|
||||
(cond
|
||||
(and (= type :restriction)
|
||||
(= code :provider-not-configured))
|
||||
(st/emit! (msg/error (tr "errors.auth-provider-not-configured")))
|
||||
(fn [cause]
|
||||
(let [{:keys [type code] :as error} (ex-data cause)]
|
||||
(cond
|
||||
(and (= type :restriction)
|
||||
(= code :provider-not-configured))
|
||||
(st/emit! (msg/error (tr "errors.auth-provider-not-configured")))
|
||||
|
||||
:else
|
||||
(st/emit! (msg/error (tr "errors.generic"))))))))
|
||||
:else
|
||||
(st/emit! (msg/error (tr "errors.generic")))))))))
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::password ::us/not-empty-string)
|
||||
|
||||
@@ -136,7 +136,8 @@
|
||||
(when login/show-alt-login-buttons?
|
||||
[:& login/login-buttons {:params params}])
|
||||
[:hr {:class (stl/css :separator)}]
|
||||
[:& register-form {:params params :on-success-callback on-success-callback}]])
|
||||
(when (contains? cf/flags :login-with-password)
|
||||
[:& register-form {:params params :on-success-callback on-success-callback}])])
|
||||
|
||||
(mf/defc register-page
|
||||
{::mf/props :obj}
|
||||
@@ -227,6 +228,7 @@
|
||||
:initial params)
|
||||
|
||||
submitted? (mf/use-state false)
|
||||
theme (when (cf/external-feature-flag "onboarding-02" "test") "light")
|
||||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
@@ -245,7 +247,8 @@
|
||||
(mf/use-fn
|
||||
(fn [form _]
|
||||
(reset! submitted? true)
|
||||
(let [params (:clean-data @form)]
|
||||
(let [params (cond-> (:clean-data @form)
|
||||
(some? theme) (assoc :theme theme))]
|
||||
(->> (rp/cmd! :register-profile params)
|
||||
(rx/finalize #(reset! submitted? false))
|
||||
(rx/subs! on-success on-error)))))]
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.1.2
|
||||
2.1.3
|
||||
|
||||
Reference in New Issue
Block a user