Skip to content

Foss main next#26

Draft
hunzlahmalik wants to merge 24 commits into
developfrom
sync/upstream-2.15.3
Draft

Foss main next#26
hunzlahmalik wants to merge 24 commits into
developfrom
sync/upstream-2.15.3

Conversation

@hunzlahmalik
Copy link
Copy Markdown

for diff comparison purposes only – do not merge

aznszn and others added 22 commits April 15, 2026 17:59
✨ Add ForwardAuth transparent SSO authentication support
The logout event was redirecting to MPASS_SIGNOUT_URL, which pointed at
oauth2-proxy's /sign_out with an rd= that depended on Cognito hosted
/logout being available. In this deployment the app client has no
hosted /logout endpoint, so the Cognito session always survives logout
and the extra redirect layer was dead weight. The URL was injected at
container start via nginx-entrypoint.sh and read from a browser global
(penpotMpassSignoutUrl), coupling the frontend bundle to identity-
provider config baked in by an external script.

Simplified to derive the portal host from the current URL:

  - Rewrite "foss-<app>.<domain>" -> "foss.<domain>" and redirect there
    after the native :logout cmd clears the Penpot session.
  - The portal host is outside ForwardAuth, so the user lands on the
    landing page instead of being silently re-authed into the
    dashboard. Re-auth still happens the next time the user clicks
    into a gated app, which is the expected behavior while Cognito
    hosted /logout is absent.

The cf/mpass-signout-url var remains defined in config.cljs for
backwards compatibility with the nginx injection script but is no
longer consumed by the logout flow.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Usama Sadiq <usama7274@gmail.com>
fix(auth): land on portal after logout, drop unreachable Cognito hop
- Configure default auth-token cookie max-age and renewal in config
- Add backend tests for env override and renew-session? behavior

Signed-off-by: jawad-khan <jawadkhan444@gmail.com>
Made-with: Cursor
✨ Add session cookie defaults and backend tests
✨ Make askii.ai the default email domain
use moneta instead of foss in logout
Replace hardcoded "moneta." prefix with an empty string so the regex
strips just the leading subdomain (e.g. app.moneta.askii.ai →
moneta.askii.ai) regardless of the deployment domain.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch to /^[^.]+\.(?=[^.]*\.[^.]*\.)/ so the subdomain is only
stripped when at least two dot-separated parts remain, preventing
over-stripping on bare domains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…omain

fix: strip first subdomain for portal redirect on signout
In x-auth-request-headers (oauth2-proxy / Cognito SSO) mode the email
is the user's identity as injected by the upstream IdP via the
X-Auth-Request-Email header on every request. Letting a user change
their Penpot email locally would diverge it from the IdP-supplied
email, and the next request would either fail to authenticate (the
auth_request middleware looks profiles up by email) or auto-create
a fresh duplicate profile under the original IdP email — losing the
user's workspace either way.

Backend
  - rpc/commands/profile.clj :: request-email-change refuses with
    :email-managed-by-external-idp when the :x-auth-request-headers
    flag is enabled, before any token is generated or any database
    write happens.
  - rpc/commands/verify_token.clj :: process-token :change-email
    refuses under the same condition. Belt-and-suspenders for tokens
    that may already exist (e.g., generated before SSO mode was
    turned on) or for any code path that bypasses the issue-side RPC.

Frontend
  - settings/profile.cljs renders the email row in two branches: in
    SSO mode (cf/mpass-signout-url is set) the row is read-only with
    no click handler and no "Change email" link; otherwise the
    upstream behaviour is preserved unchanged. Pure UX guard — the
    backend rejects the change regardless, so this just avoids
    confusing the user with an action that would fail at the API
    layer.

cf/mpass-signout-url is the SSO-mode signal injected at runtime by
docker/images/files/nginx-entrypoint.sh from the MPASS_SIGNOUT_URL
env var (the existing 3-layer logout URL configured in the devstack).

Tested manually: with :x-auth-request-headers enabled,
/#/settings/profile loads cleanly, the email field is disabled, and
the "Change email" link is not rendered.
Addresses Copilot review feedback on the email-change SSO block.

1. Frontend gated on cf/mpass-signout-url (runtime config injection
   from MPASS_SIGNOUT_URL env), backend gated on :x-auth-request-headers
   (parsed from PENPOT_FLAGS env). The two envs are independently
   settable, so a partial-config deploy could enable the backend gate
   while leaving the UI showing a button that only fails. Switch the
   frontend signal to (contains? cf/flags :x-auth-request-headers) —
   same source as the backend, so UI and RPC stay aligned regardless of
   how MPASS_SIGNOUT_URL is wired.

2. Add backend regression tests covering both gates:
   - email-change-request-blocked-when-x-auth-request-headers-enabled
     asserts request-email-change returns
     {:type :restriction :code :email-managed-by-external-idp} and
     never falls through to change-email-immediately!.
   - email-change-token-blocked-when-x-auth-request-headers-enabled
     mints a valid :change-email token, then with the flag set runs
     verify-token and asserts the same restriction — the
     belt-and-suspenders gate against pre-existing tokens.

Both tests verify the profile email row remains untouched after the
RPC rejection so a future regression that gates only one path or
performs a partial DB write is caught.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the (when (contains? cf/flags :x-auth-request-headers) ...) gate
with an unconditional refusal. This Penpot fork is SSO-only — there is
no scenario where the email-change flow should succeed:

- The auth_request middleware resolves users by email, so a local change
  diverges the profile from the IdP-asserted X-Auth-Request-Email and
  either locks the user out or pre-stages a takeover for the next
  victim's first sign-in.
- All other authentication paths are disabled in this fork.

Conditional gating left dead branches (change-email-immediately!,
request-email-change!, the SMTP-fallback fork) sitting one flag-flip
away from re-introducing the takeover. Removing them shrinks the
attack surface to a single ex/raise.

Backend
- profile.clj: defmethod ::request-email-change reduced to ex/raise.
  Removed change-email-immediately! and request-email-change! private
  fns + their forward declares (now unreachable).
- verify_token.clj: defmethod process-token :change-email reduced to
  ex/raise. Removed the let body that performed the actual update.

Frontend
- profile.cljs: removed the if/else branch that toggled the change-email
  link based on cf/flags. Email row is always read-only. Drops the
  unreachable on-show-change-email handler.

Tests
- Replaced email-change-request, email-change-request-without-smtp, and
  the two newly-added flag-conditional tests with two unconditional
  tests: email-change-request-is-disabled and
  email-change-token-is-rejected. Both assert the rejection AND that
  the profile row is not mutated, catching a future regression that
  performs a partial DB write.

Net: -245 / +64 lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(sso): block email changes when x-auth-request-headers is enabled
* feat: Add user in default workspace on login
* fix: fixed workspace issue
* ✨ Auto join SSO users to provisioned
* fix: update backend/src/app/http/auth_request.clj
Signed-off-by: Usama Sadiq <usama7274@gmail.com>
---------

Co-authored-by: Usama Sadiq <usama.sadiq@arbisoft.com>
…n-minimal

🐛 Re-key session when X-Auth-Request identity differs
# Conflicts:
#	backend/test/backend_tests/rpc_profile_test.clj
@hunzlahmalik hunzlahmalik mentioned this pull request May 19, 2026
hunzlahmalik and others added 2 commits May 20, 2026 12:06
On macOS Docker Desktop, Node's fs.cpSync writes files in the bind
mount with a com.docker.grpcfuse.ownership xattr declaring mode 200
even when the host inode is 644. The subsequent `cp -a` (and the
original `rsync -avr`) then chmod the destination to 200, which lands
on the real host inode and renders the bundle unreadable to anything
outside the build container (Linux containers via gRPC-FUSE included).

chmod-ing packages/server/dist before the copy rewrites the xattr to
644 while the host mode is still 644, so cp -a propagates the correct
mode and the bundle is readable downstream.

Behaviour on Linux hosts is unchanged: gRPC-FUSE only runs on macOS
Docker Desktop; chmod -R is a no-op when modes are already correct.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…attr

fix(mcp-build): normalize dist modes before cp -a (macOS gRPC-FUSE)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants