Skip to content

Preserve deep-link when a guest hits a private-tree URL (#5388)#5409

Draft
jbh4x82 wants to merge 1 commit into
fisharebest:mainfrom
jbh4x82:fix/private-tree-deeplink-login-redirect
Draft

Preserve deep-link when a guest hits a private-tree URL (#5388)#5409
jbh4x82 wants to merge 1 commit into
fisharebest:mainfrom
jbh4x82:fix/private-tree-deeplink-login-redirect

Conversation

@jbh4x82

@jbh4x82 jbh4x82 commented Jun 27, 2026

Copy link
Copy Markdown

Fixes the deep-link loss described in #5388.

Problem

When an unauthenticated visitor follows a deep link into a private tree (e.g. an email link to /tree/<name>/individual/I1, or any custom-module tree URL), TreeService::all() hides the tree from them, so Router::process() reaches its {tree} fall-through and defers to NotFound, which redirect()s to the home page. The original URL is discarded, so after signing in the visitor lands on the home page instead of the page they clicked. This breaks "click this link to see the post" workflows on sites with private trees.

Why here, and not in NotFound

As you noted in #5388, rendering a 404 in place at the tree URL would be a dead-end (no valid tree → no genealogy menu). This change avoids that by redirecting guests to the login page carrying the requested URL, which is a normal navigable page. Router::process() is also where the information actually is: we already know the matched route required {tree} and that the tree was not resolvable for this user, so NotFound itself stays stock.

What it does

At the {tree}-null branch:

  • Guestredirect(route(LoginPage::class, ['url' => (string) $request->getUri()])). This is the same pattern AuthLoggedIn and AuthMember already use for auth-required routes. LoginPage reads that URL and LoginAction redirects to it on success, both through isLocalUrl(), so it is not an open redirect. It also avoids disclosing to a guest whether a private tree exists: a missing tree and a private-but-inaccessible tree are handled identically.
  • Logged-in user → unchanged (falls through to NotFound as before). Signing in again would not help, and we must not mislabel a genuinely missing tree as access-denied.

Testing

  • webtrees main + php -l on this branch; behaviour confirmed on 2.2.6 / PHP 8.3.
  • Guest deep-link to a private tree (individual, family, and a custom-module route) → login page → after sign-in lands on the originally-requested URL.
  • Guest deep-link to a public tree → unchanged (tree resolves, no redirect).
  • Logged-in member without access → unchanged.
  • Non-{tree} 404s (typos, unknown routes) → unchanged.

Happy to adjust. For example, if you'd prefer the logged-in-but-no-access case to render HttpAccessDeniedException rather than fall through, that's a small addition, though it needs distinguishing a private tree from a missing one (an extra gedcom-table lookup), which is why I left it out here.

When an unauthenticated visitor follows a deep link into a private tree
(e.g. an email link to /tree/<name>/individual/I1, or a custom-module
tree URL), TreeService::all() hides the tree, so Router::process() falls
through to NotFound, which redirects to the home page and discards the
original URL. After signing in the visitor lands on the home page
instead of the page they originally clicked.

Redirect guests to the login page with the requested URL preserved - the
same pattern AuthLoggedIn/AuthMember already use for auth-required
routes. LoginPage and LoginAction both validate the URL with
isLocalUrl(), so it is not an open redirect. Logged-in users keep the
existing behaviour, and we never reveal whether a private tree exists.

Refs fisharebest#5388
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.

1 participant