Skip to content
2 changes: 2 additions & 0 deletions docker/images/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
2 changes: 2 additions & 0 deletions docker/images/files/config.js
Original file line number Diff line number Diff line change
@@ -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 = "";
31 changes: 31 additions & 0 deletions docker/images/files/nginx-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,39 @@ update_mpass_signout_url() {
fi
}

# AUTH_TYPE (e.g. SSO): writes penpotAuthType into frontend config.js.
#
# 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 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_norm}\";#g" \
"$1" > "$tmp"; then
rm -f "$tmp"
return 1
fi
if ! mv "$tmp" "$1"; then
rm -f "$tmp"
return 1
fi
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
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/app/config.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
18 changes: 15 additions & 3 deletions frontend/src/app/main/ui/settings.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
(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]
[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]
Expand Down Expand Up @@ -40,9 +42,14 @@

(hooks/use-shortcuts ::dashboard sc/shortcuts)

(mf/with-effect [profile]
(mf/with-effect [section profile]
(when (nil? profile)
(st/emit! (rt/assign-exception {:type :authentication}))))
(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 {} {::rt/replace true}))))

[:*
[:> modal-container*]
Expand All @@ -65,7 +72,12 @@
:error-href error-href}]

:settings-password
[:& password-page]
(if (cf/auth-type-sso?)
(if (some? profile)
[:& profile-page]
[:> loader*
{:title (tr "labels.loading") :overlay false}])
[:& password-page])

:settings-options
[:& options-page]
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/app/main/ui/settings/sidebar.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading