Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Comment on lines +48 to +68
}

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?))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot is this resolved now or not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this was resolved. The redirect is now guarded with (some? profile) (line 50), so it only fires after the profile is loaded. While profile is nil and SSO is active, the :settings-password case renders a loading spinner instead (lines 76-79), preventing any race between the auth exception and the redirect.

(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])
Comment on lines 74 to +80

: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