From 4a74228c5705647611ab68f9fe48583ac7d2914d Mon Sep 17 00:00:00 2001 From: jawad-khan Date: Wed, 20 May 2026 15:43:37 +0500 Subject: [PATCH 1/7] :sparkles: Hide password settings when AUTH_TYPE indicates SSO --- docker/images/docker-compose.yaml | 2 ++ docker/images/files/config.js | 2 ++ docker/images/files/nginx-entrypoint.sh | 10 ++++++++++ frontend/src/app/config.cljs | 10 ++++++++++ frontend/src/app/main/ui/settings.cljs | 12 +++++++++++- frontend/src/app/main/ui/settings/sidebar.cljs | 9 +++++---- 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/docker/images/docker-compose.yaml b/docker/images/docker-compose.yaml index 25d419a354e..621df175582 100644 --- a/docker/images/docker-compose.yaml +++ b/docker/images/docker-compose.yaml @@ -106,6 +106,8 @@ services: environment: << : [*penpot-flags, *penpot-http-body-size, *penpot-public-uri] + # Uncomment for SSO: hides Account → Password (injected into config.js by the frontend image entrypoint). + # AUTH_TYPE: SSO penpot-backend: image: "penpotapp/backend:${PENPOT_VERSION:-2.15}" diff --git a/docker/images/files/config.js b/docker/images/files/config.js index a9f444caa64..967b3b8be25 100644 --- a/docker/images/files/config.js +++ b/docker/images/files/config.js @@ -1,3 +1,5 @@ // Frontend configuration //var penpotFlags = ""; //var penpotMpassSignoutUrl = ""; +// Deploy-time auth mode (injected via AUTH_TYPE env, e.g. SSO). Matches case-insensitively; "sso" hides password settings. +//var penpotAuthType = ""; diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index ace4bdfb544..66f91c6673b 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -38,8 +38,18 @@ update_mpass_signout_url() { fi } +# AUTH_TYPE (e.g. SSO): consumed by frontend to hide native password UX. +update_auth_type() { + if [ -n "$AUTH_TYPE" ]; then + echo "$(sed \ + -e "s|^//var penpotAuthType = .*;|var penpotAuthType = \"$AUTH_TYPE\";|g" \ + "$1")" > "$1" + fi +} + update_flags /var/www/app/js/config.js update_mpass_signout_url /var/www/app/js/config.js +update_auth_type /var/www/app/js/config.js ######################################### ## Nginx Config diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index ae9f153d386..94f99aa319c 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -161,6 +161,16 @@ ;; penpot /auth/login screen so the oauth2-proxy cookie and Cognito ;; session are also cleared. Nil on non-SSO deployments. (def mpass-signout-url (obj/get global "penpotMpassSignoutUrl")) + +(defn ^boolean auth-type-sso? + "True when `penpotAuthType` is set in config.js from deploy env AUTH_TYPE, + normalized to SSO (case-insensitive). Enables hiding password/account flows + backed by Penpot-local credentials." + [] + (let [v (obj/get global "penpotAuthType")] + (and (string? v) + (= "sso" (-> v str/trim str/lower))))) + (def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/")) (def grid-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/")) (def plugins-list-uri (obj/get global "penpotPluginsListURI" "https://penpot.app/penpothub/plugins")) diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 2cb617c9392..85fc653cd7e 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.settings (:require-macros [app.main.style :as stl]) (:require + [app.config :as cf] [app.main.data.dashboard.shortcuts :as sc] [app.main.refs :as refs] [app.main.router :as rt] @@ -18,6 +19,7 @@ [app.main.ui.settings.feedback :refer [feedback-page*]] [app.main.ui.settings.integrations :refer [integrations-page*]] [app.main.ui.settings.notifications :refer [notifications-page*]] + [app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.settings.options :refer [options-page]] [app.main.ui.settings.password :refer [password-page]] [app.main.ui.settings.profile :refer [profile-page]] @@ -40,6 +42,11 @@ (hooks/use-shortcuts ::dashboard sc/shortcuts) + (mf/with-effect [section] + (when (and (= section :settings-password) + (cf/auth-type-sso?)) + (st/emit! (rt/nav :settings-profile)))) + (mf/with-effect [profile] (when (nil? profile) (st/emit! (rt/assign-exception {:type :authentication})))) @@ -65,7 +72,10 @@ :error-href error-href}] :settings-password - [:& password-page] + (if (cf/auth-type-sso?) + [:> loader* + {:title (tr "labels.loading") :overlay false}] + [:& password-page]) :settings-options [:& options-page] diff --git a/frontend/src/app/main/ui/settings/sidebar.cljs b/frontend/src/app/main/ui/settings/sidebar.cljs index 7f35f4787fa..66778daa1ac 100644 --- a/frontend/src/app/main/ui/settings/sidebar.cljs +++ b/frontend/src/app/main/ui/settings/sidebar.cljs @@ -91,10 +91,11 @@ :on-click go-settings-profile} [:span {:class (stl/css :element-title)} (tr "labels.profile")]] - [:li {:class (stl/css-case :current password? - :settings-item true) - :on-click go-settings-password} - [:span {:class (stl/css :element-title)} (tr "labels.password")]] + (when-not (cf/auth-type-sso?) + [:li {:class (stl/css-case :current password? + :settings-item true) + :on-click go-settings-password} + [:span {:class (stl/css :element-title)} (tr "labels.password")]]) [:li {:class (stl/css-case :current notifications? :settings-item true) From f0867ec9122b16b1c9762428085f500f793053be Mon Sep 17 00:00:00 2001 From: jawad-khan Date: Wed, 20 May 2026 16:19:13 +0500 Subject: [PATCH 2/7] :bug: Address Copilot review on AUTH_TYPE SSO settings --- docker/images/files/nginx-entrypoint.sh | 9 +++++++-- frontend/src/app/main/ui/settings.cljs | 10 ++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index 66f91c6673b..cf25bb4a0ef 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -39,10 +39,15 @@ update_mpass_signout_url() { } # AUTH_TYPE (e.g. SSO): consumed by frontend to hide native password UX. +# +# `#` separates pattern/replacement because values may contain "/" or "|". +# Escape `\`, `"`, and `&` so the generated JS literal and sed replacement stay valid. update_auth_type() { - if [ -n "$AUTH_TYPE" ]; then + if [ -n "${AUTH_TYPE:-}" ]; then + local auth_esc + auth_esc=$(printf '%s' "$AUTH_TYPE" | sed -e 's/\\/\\\\/g' -e 's/&/\\\&/g' -e 's/"/\\"/g') echo "$(sed \ - -e "s|^//var penpotAuthType = .*;|var penpotAuthType = \"$AUTH_TYPE\";|g" \ + -e "s#^//var penpotAuthType = .*;#var penpotAuthType = \"${auth_esc}\";#g" \ "$1")" > "$1" fi } diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 85fc653cd7e..82d0138b2ce 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -12,6 +12,7 @@ [app.main.refs :as refs] [app.main.router :as rt] [app.main.store :as st] + [app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.hooks :as hooks] [app.main.ui.modal :refer [modal-container*]] [app.main.ui.settings.change-email] @@ -19,7 +20,6 @@ [app.main.ui.settings.feedback :refer [feedback-page*]] [app.main.ui.settings.integrations :refer [integrations-page*]] [app.main.ui.settings.notifications :refer [notifications-page*]] - [app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.settings.options :refer [options-page]] [app.main.ui.settings.password :refer [password-page]] [app.main.ui.settings.profile :refer [profile-page]] @@ -42,15 +42,13 @@ (hooks/use-shortcuts ::dashboard sc/shortcuts) - (mf/with-effect [section] + (mf/with-effect [section profile] + (when (nil? profile) + (st/emit! (rt/assign-exception {:type :authentication}))) (when (and (= section :settings-password) (cf/auth-type-sso?)) (st/emit! (rt/nav :settings-profile)))) - (mf/with-effect [profile] - (when (nil? profile) - (st/emit! (rt/assign-exception {:type :authentication})))) - [:* [:> modal-container*] [:section {:class (stl/css :dashboard-layout-refactor :dashboard)} From 877b5ed5b3368019fe2bea48bfd4f63e468fd51e Mon Sep 17 00:00:00 2001 From: jawad-khan Date: Wed, 20 May 2026 16:31:35 +0500 Subject: [PATCH 3/7] :bug: Escape # in AUTH_TYPE injection; show profile for SSO password route --- docker/images/files/nginx-entrypoint.sh | 8 ++++++-- frontend/src/app/main/ui/settings.cljs | 5 ++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index cf25bb4a0ef..f64e0d76b05 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -41,11 +41,15 @@ update_mpass_signout_url() { # AUTH_TYPE (e.g. SSO): consumed by frontend to hide native password UX. # # `#` separates pattern/replacement because values may contain "/" or "|". -# Escape `\`, `"`, and `&` so the generated JS literal and sed replacement stay valid. +# Escape `\`, `#`, `"`, and `&` so the generated JS literal and sed replacement stay valid. update_auth_type() { if [ -n "${AUTH_TYPE:-}" ]; then local auth_esc - auth_esc=$(printf '%s' "$AUTH_TYPE" | sed -e 's/\\/\\\\/g' -e 's/&/\\\&/g' -e 's/"/\\"/g') + auth_esc=$(printf '%s' "$AUTH_TYPE" | sed \ + -e 's/\\/\\\\/g' \ + -e 's/#/\\#/g' \ + -e 's/&/\\\&/g' \ + -e 's/"/\\"/g') echo "$(sed \ -e "s#^//var penpotAuthType = .*;#var penpotAuthType = \"${auth_esc}\";#g" \ "$1")" > "$1" diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 82d0138b2ce..c31ca0b7596 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -12,7 +12,6 @@ [app.main.refs :as refs] [app.main.router :as rt] [app.main.store :as st] - [app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.hooks :as hooks] [app.main.ui.modal :refer [modal-container*]] [app.main.ui.settings.change-email] @@ -71,8 +70,8 @@ :settings-password (if (cf/auth-type-sso?) - [:> loader* - {:title (tr "labels.loading") :overlay false}] + ;; Prefer profile UX over a loader until `rt/nav` updates the fragment. + [:& profile-page] [:& password-page]) :settings-options From 4c11066ab7313573d9409cc0dab1e566b63d2674 Mon Sep 17 00:00:00 2001 From: jawad-khan Date: Wed, 20 May 2026 17:48:59 +0500 Subject: [PATCH 4/7] :bug: Harden AUTH_TYPE sed write and SSO settings redirect sequencing --- docker/images/files/nginx-entrypoint.sh | 22 +++++++++++++++------- frontend/src/app/main/ui/settings.cljs | 9 +++++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index f64e0d76b05..080e16393a2 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -38,21 +38,29 @@ update_mpass_signout_url() { fi } -# AUTH_TYPE (e.g. SSO): consumed by frontend to hide native password UX. -# -# `#` separates pattern/replacement because values may contain "/" or "|". -# Escape `\`, `#`, `"`, and `&` so the generated JS literal and sed replacement stay valid. +# AUTH_TYPE (e.g. SSO): writes penpotAuthType into frontend config.js. +# Substitution uses '#' as the sed delimiter (values may include "/" or "|"). +# Prefix escapes for \, #, ", & before embedding AUTH_TYPE into a JS double-quoted string. +# Writes via a temp file + mv so a failed/interrupted sed cannot truncate config.js. update_auth_type() { if [ -n "${AUTH_TYPE:-}" ]; then - local auth_esc + local auth_esc tmp auth_esc=$(printf '%s' "$AUTH_TYPE" | sed \ -e 's/\\/\\\\/g' \ -e 's/#/\\#/g' \ -e 's/&/\\\&/g' \ -e 's/"/\\"/g') - echo "$(sed \ + tmp="$(mktemp)" || return 1 + if ! sed \ -e "s#^//var penpotAuthType = .*;#var penpotAuthType = \"${auth_esc}\";#g" \ - "$1")" > "$1" + "$1" > "$tmp"; then + rm -f "$tmp" + return 1 + fi + if ! mv "$tmp" "$1"; then + rm -f "$tmp" + return 1 + fi fi } diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index c31ca0b7596..bfa266d5d65 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -12,6 +12,7 @@ [app.main.refs :as refs] [app.main.router :as rt] [app.main.store :as st] + [app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.hooks :as hooks] [app.main.ui.modal :refer [modal-container*]] [app.main.ui.settings.change-email] @@ -44,7 +45,9 @@ (mf/with-effect [section profile] (when (nil? profile) (st/emit! (rt/assign-exception {:type :authentication}))) + ;; Wait for profile so we don't race assign-exception vs redirect on cold load. (when (and (= section :settings-password) + (some? profile) (cf/auth-type-sso?)) (st/emit! (rt/nav :settings-profile)))) @@ -70,8 +73,10 @@ :settings-password (if (cf/auth-type-sso?) - ;; Prefer profile UX over a loader until `rt/nav` updates the fragment. - [:& profile-page] + (if (some? profile) + [:& profile-page] + [:> loader* + {:title (tr "labels.loading") :overlay false}]) [:& password-page]) :settings-options From 5656a2387a4d695cbd94d78ccdcb148b88f90d16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 12:58:37 +0000 Subject: [PATCH 5/7] :bug: Normalize AUTH_TYPE to lowercase and use replace nav for SSO redirect Agent-Logs-Url: https://github.com/Pressingly/penpot/sessions/4698e215-0f97-44d4-8097-664912d66b92 Co-authored-by: jawad-khan <5320368+jawad-khan@users.noreply.github.com> --- docker/images/files/nginx-entrypoint.sh | 12 +++++++++--- frontend/src/app/main/ui/settings.cljs | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index 080e16393a2..a07df3ef084 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -40,12 +40,18 @@ update_mpass_signout_url() { # AUTH_TYPE (e.g. SSO): writes penpotAuthType into frontend config.js. # Substitution uses '#' as the sed delimiter (values may include "/" or "|"). -# Prefix escapes for \, #, ", & before embedding AUTH_TYPE into a JS double-quoted string. +# Normalises AUTH_TYPE: strip control characters, trim whitespace, lowercase. +# Then escapes \, #, ", & before embedding into a JS double-quoted string. # Writes via a temp file + mv so a failed/interrupted sed cannot truncate config.js. update_auth_type() { if [ -n "${AUTH_TYPE:-}" ]; then - local auth_esc tmp - auth_esc=$(printf '%s' "$AUTH_TYPE" | sed \ + local auth_norm auth_esc tmp + # Strip control chars, trim leading/trailing whitespace, convert to lowercase. + auth_norm=$(printf '%s' "$AUTH_TYPE" \ + | tr -d '[:cntrl:]' \ + | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \ + | tr '[:upper:]' '[:lower:]') + auth_esc=$(printf '%s' "$auth_norm" | sed \ -e 's/\\/\\\\/g' \ -e 's/#/\\#/g' \ -e 's/&/\\\&/g' \ diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index bfa266d5d65..766150ff108 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -49,7 +49,7 @@ (when (and (= section :settings-password) (some? profile) (cf/auth-type-sso?)) - (st/emit! (rt/nav :settings-profile)))) + (st/emit! (rt/nav :settings-profile {} {::rt/replace true})))) [:* [:> modal-container*] From 452b170d1f2c1d436c94f67f67bb7fbff3ca400f Mon Sep 17 00:00:00 2001 From: jawad-khan Date: Wed, 20 May 2026 18:06:51 +0500 Subject: [PATCH 6/7] :bug: Fix Copilot review: normalize AUTH_TYPE in entrypoint; replace-token SSO redirect --- docker/images/files/nginx-entrypoint.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index a07df3ef084..8992fe4cde9 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -39,18 +39,18 @@ update_mpass_signout_url() { } # AUTH_TYPE (e.g. SSO): writes penpotAuthType into frontend config.js. -# Substitution uses '#' as the sed delimiter (values may include "/" or "|"). -# Normalises AUTH_TYPE: strip control characters, trim whitespace, lowercase. -# Then escapes \, #, ", & before embedding into a JS double-quoted string. -# Writes via a temp file + mv so a failed/interrupted sed cannot truncate config.js. +# +# '#' is the sed delimiter so values may include "/" or "|". +# Normalize: strip POSIX [:cntrl:], trim [:space:], fold case (ASCII uppercase to lowercase). +# Escape \, #, ", & for a JS double-quoted literal. Writes via tmp + mv only on success. update_auth_type() { if [ -n "${AUTH_TYPE:-}" ]; then local auth_norm auth_esc tmp - # Strip control chars, trim leading/trailing whitespace, convert to lowercase. auth_norm=$(printf '%s' "$AUTH_TYPE" \ - | tr -d '[:cntrl:]' \ + | LC_ALL=C tr -d '[:cntrl:]' \ | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \ - | tr '[:upper:]' '[:lower:]') + | LC_ALL=C tr '[:upper:]' '[:lower:]') + [ -n "$auth_norm" ] || return 0 auth_esc=$(printf '%s' "$auth_norm" | sed \ -e 's/\\/\\\\/g' \ -e 's/#/\\#/g' \ From de9566c4a415ee4c2ed462808f36e04b794ffa19 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 13:12:47 +0000 Subject: [PATCH 7/7] :bug: Whitelist AUTH_TYPE and fix mktemp cross-filesystem issue Agent-Logs-Url: https://github.com/Pressingly/penpot/sessions/c4ba84fe-6203-434b-bd9b-6a3453203439 Co-authored-by: jawad-khan <5320368+jawad-khan@users.noreply.github.com> --- docker/images/files/nginx-entrypoint.sh | 30 ++++++++++++------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index 8992fe4cde9..3008eaac3c0 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -40,25 +40,23 @@ update_mpass_signout_url() { # AUTH_TYPE (e.g. SSO): writes penpotAuthType into frontend config.js. # -# '#' is the sed delimiter so values may include "/" or "|". -# Normalize: strip POSIX [:cntrl:], trim [:space:], fold case (ASCII uppercase to lowercase). -# Escape \, #, ", & for a JS double-quoted literal. Writes via tmp + mv only on success. +# Normalize: fold to lowercase, then validate against a strict whitelist of +# safe characters ([a-z0-9_-]). Any value that contains special characters +# (spaces, #, &, \, ", newlines, …) is rejected and the line is left commented. +# The temp file is created in the same directory as the target so that the +# final 'mv' is always an atomic rename on the same filesystem (avoids EXDEV). update_auth_type() { if [ -n "${AUTH_TYPE:-}" ]; then - local auth_norm auth_esc tmp - auth_norm=$(printf '%s' "$AUTH_TYPE" \ - | LC_ALL=C tr -d '[:cntrl:]' \ - | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \ - | LC_ALL=C tr '[:upper:]' '[:lower:]') - [ -n "$auth_norm" ] || return 0 - auth_esc=$(printf '%s' "$auth_norm" | sed \ - -e 's/\\/\\\\/g' \ - -e 's/#/\\#/g' \ - -e 's/&/\\\&/g' \ - -e 's/"/\\"/g') - tmp="$(mktemp)" || return 1 + local auth_norm tmp + auth_norm=$(printf '%s' "$AUTH_TYPE" | LC_ALL=C tr '[:upper:]' '[:lower:]') + # Reject values that contain anything outside a-z 0-9 _ - + if ! [[ "$auth_norm" =~ ^[a-z0-9_-]+$ ]]; then + echo "update_auth_type: AUTH_TYPE contains invalid characters; skipping injection" >&2 + return 0 + fi + tmp="$(mktemp -p "$(dirname "$1")")" || return 1 if ! sed \ - -e "s#^//var penpotAuthType = .*;#var penpotAuthType = \"${auth_esc}\";#g" \ + -e "s#^//var penpotAuthType = .*;#var penpotAuthType = \"${auth_norm}\";#g" \ "$1" > "$tmp"; then rm -f "$tmp" return 1